@loopingai/core 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 (238) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +279 -0
  3. package/dist/a2a/agent-stub.d.ts +61 -0
  4. package/dist/a2a/agent-stub.d.ts.map +1 -0
  5. package/dist/a2a/agent-stub.js +2 -0
  6. package/dist/a2a/agent-stub.js.map +1 -0
  7. package/dist/a2a/card.d.ts +119 -0
  8. package/dist/a2a/card.d.ts.map +1 -0
  9. package/dist/a2a/card.js +115 -0
  10. package/dist/a2a/card.js.map +1 -0
  11. package/dist/a2a/context.d.ts +14 -0
  12. package/dist/a2a/context.d.ts.map +1 -0
  13. package/dist/a2a/context.js +55 -0
  14. package/dist/a2a/context.js.map +1 -0
  15. package/dist/a2a/executor.d.ts +85 -0
  16. package/dist/a2a/executor.d.ts.map +1 -0
  17. package/dist/a2a/executor.js +107 -0
  18. package/dist/a2a/executor.js.map +1 -0
  19. package/dist/a2a/index.d.ts +18 -0
  20. package/dist/a2a/index.d.ts.map +1 -0
  21. package/dist/a2a/index.js +17 -0
  22. package/dist/a2a/index.js.map +1 -0
  23. package/dist/a2a/notify.d.ts +75 -0
  24. package/dist/a2a/notify.d.ts.map +1 -0
  25. package/dist/a2a/notify.js +161 -0
  26. package/dist/a2a/notify.js.map +1 -0
  27. package/dist/a2a/parts.d.ts +46 -0
  28. package/dist/a2a/parts.d.ts.map +1 -0
  29. package/dist/a2a/parts.js +95 -0
  30. package/dist/a2a/parts.js.map +1 -0
  31. package/dist/a2a/task-store.d.ts +45 -0
  32. package/dist/a2a/task-store.d.ts.map +1 -0
  33. package/dist/a2a/task-store.js +100 -0
  34. package/dist/a2a/task-store.js.map +1 -0
  35. package/dist/a2a/task.d.ts +86 -0
  36. package/dist/a2a/task.d.ts.map +1 -0
  37. package/dist/a2a/task.js +16 -0
  38. package/dist/a2a/task.js.map +1 -0
  39. package/dist/a2a/verify.d.ts +64 -0
  40. package/dist/a2a/verify.d.ts.map +1 -0
  41. package/dist/a2a/verify.js +122 -0
  42. package/dist/a2a/verify.js.map +1 -0
  43. package/dist/agent/budget.d.ts +47 -0
  44. package/dist/agent/budget.d.ts.map +1 -0
  45. package/dist/agent/budget.js +43 -0
  46. package/dist/agent/budget.js.map +1 -0
  47. package/dist/agent/control.d.ts +110 -0
  48. package/dist/agent/control.d.ts.map +1 -0
  49. package/dist/agent/control.js +115 -0
  50. package/dist/agent/control.js.map +1 -0
  51. package/dist/agent/final-reply.d.ts +49 -0
  52. package/dist/agent/final-reply.d.ts.map +1 -0
  53. package/dist/agent/final-reply.js +52 -0
  54. package/dist/agent/final-reply.js.map +1 -0
  55. package/dist/agent/history.d.ts +83 -0
  56. package/dist/agent/history.d.ts.map +1 -0
  57. package/dist/agent/history.js +117 -0
  58. package/dist/agent/history.js.map +1 -0
  59. package/dist/agent/index.d.ts +17 -0
  60. package/dist/agent/index.d.ts.map +1 -0
  61. package/dist/agent/index.js +17 -0
  62. package/dist/agent/index.js.map +1 -0
  63. package/dist/agent/inference.d.ts +52 -0
  64. package/dist/agent/inference.d.ts.map +1 -0
  65. package/dist/agent/inference.js +79 -0
  66. package/dist/agent/inference.js.map +1 -0
  67. package/dist/agent/model.d.ts +75 -0
  68. package/dist/agent/model.d.ts.map +1 -0
  69. package/dist/agent/model.js +62 -0
  70. package/dist/agent/model.js.map +1 -0
  71. package/dist/agent/session.d.ts +92 -0
  72. package/dist/agent/session.d.ts.map +1 -0
  73. package/dist/agent/session.js +80 -0
  74. package/dist/agent/session.js.map +1 -0
  75. package/dist/config.d.ts +159 -0
  76. package/dist/config.d.ts.map +1 -0
  77. package/dist/config.js +92 -0
  78. package/dist/config.js.map +1 -0
  79. package/dist/contract/index.d.ts +10 -0
  80. package/dist/contract/index.d.ts.map +1 -0
  81. package/dist/contract/index.js +9 -0
  82. package/dist/contract/index.js.map +1 -0
  83. package/dist/contract/plugin.d.ts +163 -0
  84. package/dist/contract/plugin.d.ts.map +1 -0
  85. package/dist/contract/plugin.js +39 -0
  86. package/dist/contract/plugin.js.map +1 -0
  87. package/dist/contract/recipe.d.ts +170 -0
  88. package/dist/contract/recipe.d.ts.map +1 -0
  89. package/dist/contract/recipe.js +2 -0
  90. package/dist/contract/recipe.js.map +1 -0
  91. package/dist/contract/validation.d.ts +86 -0
  92. package/dist/contract/validation.d.ts.map +1 -0
  93. package/dist/contract/validation.js +76 -0
  94. package/dist/contract/validation.js.map +1 -0
  95. package/dist/db/db.d.ts +131 -0
  96. package/dist/db/db.d.ts.map +1 -0
  97. package/dist/db/db.js +91 -0
  98. package/dist/db/db.js.map +1 -0
  99. package/dist/db/index.d.ts +9 -0
  100. package/dist/db/index.d.ts.map +1 -0
  101. package/dist/db/index.js +9 -0
  102. package/dist/db/index.js.map +1 -0
  103. package/dist/db/migrations/index.d.ts +21 -0
  104. package/dist/db/migrations/index.d.ts.map +1 -0
  105. package/dist/db/migrations/index.js +54 -0
  106. package/dist/db/migrations/index.js.map +1 -0
  107. package/dist/db/models/subtasks.d.ts +102 -0
  108. package/dist/db/models/subtasks.d.ts.map +1 -0
  109. package/dist/db/models/subtasks.js +285 -0
  110. package/dist/db/models/subtasks.js.map +1 -0
  111. package/dist/db/models/tasks.d.ts +92 -0
  112. package/dist/db/models/tasks.d.ts.map +1 -0
  113. package/dist/db/models/tasks.js +212 -0
  114. package/dist/db/models/tasks.js.map +1 -0
  115. package/dist/db/schema.d.ts +488 -0
  116. package/dist/db/schema.d.ts.map +1 -0
  117. package/dist/db/schema.js +91 -0
  118. package/dist/db/schema.js.map +1 -0
  119. package/dist/env.d.ts +46 -0
  120. package/dist/env.d.ts.map +1 -0
  121. package/dist/env.js +40 -0
  122. package/dist/env.js.map +1 -0
  123. package/dist/index.d.ts +20 -0
  124. package/dist/index.d.ts.map +1 -0
  125. package/dist/index.js +18 -0
  126. package/dist/index.js.map +1 -0
  127. package/dist/platform.d.ts +47 -0
  128. package/dist/platform.d.ts.map +1 -0
  129. package/dist/platform.js +47 -0
  130. package/dist/platform.js.map +1 -0
  131. package/dist/runtime/index.d.ts +86 -0
  132. package/dist/runtime/index.d.ts.map +1 -0
  133. package/dist/runtime/index.js +123 -0
  134. package/dist/runtime/index.js.map +1 -0
  135. package/dist/runtime/tool-families.d.ts +33 -0
  136. package/dist/runtime/tool-families.d.ts.map +1 -0
  137. package/dist/runtime/tool-families.js +62 -0
  138. package/dist/runtime/tool-families.js.map +1 -0
  139. package/dist/subagent/fingerprint.d.ts +38 -0
  140. package/dist/subagent/fingerprint.d.ts.map +1 -0
  141. package/dist/subagent/fingerprint.js +97 -0
  142. package/dist/subagent/fingerprint.js.map +1 -0
  143. package/dist/subagent/index.d.ts +147 -0
  144. package/dist/subagent/index.d.ts.map +1 -0
  145. package/dist/subagent/index.js +299 -0
  146. package/dist/subagent/index.js.map +1 -0
  147. package/dist/subagent/prompt.d.ts +34 -0
  148. package/dist/subagent/prompt.d.ts.map +1 -0
  149. package/dist/subagent/prompt.js +46 -0
  150. package/dist/subagent/prompt.js.map +1 -0
  151. package/dist/subagent/run.d.ts +151 -0
  152. package/dist/subagent/run.d.ts.map +1 -0
  153. package/dist/subagent/run.js +491 -0
  154. package/dist/subagent/run.js.map +1 -0
  155. package/dist/subagent/workspace.d.ts +65 -0
  156. package/dist/subagent/workspace.d.ts.map +1 -0
  157. package/dist/subagent/workspace.js +53 -0
  158. package/dist/subagent/workspace.js.map +1 -0
  159. package/dist/subtasks/catalog.d.ts +40 -0
  160. package/dist/subtasks/catalog.d.ts.map +1 -0
  161. package/dist/subtasks/catalog.js +36 -0
  162. package/dist/subtasks/catalog.js.map +1 -0
  163. package/dist/subtasks/decomposition.d.ts +90 -0
  164. package/dist/subtasks/decomposition.d.ts.map +1 -0
  165. package/dist/subtasks/decomposition.js +205 -0
  166. package/dist/subtasks/decomposition.js.map +1 -0
  167. package/dist/subtasks/delegate.d.ts +77 -0
  168. package/dist/subtasks/delegate.d.ts.map +1 -0
  169. package/dist/subtasks/delegate.js +102 -0
  170. package/dist/subtasks/delegate.js.map +1 -0
  171. package/dist/subtasks/index.d.ts +11 -0
  172. package/dist/subtasks/index.d.ts.map +1 -0
  173. package/dist/subtasks/index.js +11 -0
  174. package/dist/subtasks/index.js.map +1 -0
  175. package/dist/subtasks/scheduler.d.ts +49 -0
  176. package/dist/subtasks/scheduler.d.ts.map +1 -0
  177. package/dist/subtasks/scheduler.js +48 -0
  178. package/dist/subtasks/scheduler.js.map +1 -0
  179. package/dist/subtasks/subtask-types.d.ts +100 -0
  180. package/dist/subtasks/subtask-types.d.ts.map +1 -0
  181. package/dist/subtasks/subtask-types.js +111 -0
  182. package/dist/subtasks/subtask-types.js.map +1 -0
  183. package/dist/subtasks/types.d.ts +321 -0
  184. package/dist/subtasks/types.d.ts.map +1 -0
  185. package/dist/subtasks/types.js +16 -0
  186. package/dist/subtasks/types.js.map +1 -0
  187. package/dist/testing/auth.d.ts +12 -0
  188. package/dist/testing/auth.d.ts.map +1 -0
  189. package/dist/testing/auth.js +25 -0
  190. package/dist/testing/auth.js.map +1 -0
  191. package/dist/testing/do.d.ts +30 -0
  192. package/dist/testing/do.d.ts.map +1 -0
  193. package/dist/testing/do.js +26 -0
  194. package/dist/testing/do.js.map +1 -0
  195. package/dist/testing/fake-session.d.ts +27 -0
  196. package/dist/testing/fake-session.d.ts.map +1 -0
  197. package/dist/testing/fake-session.js +38 -0
  198. package/dist/testing/fake-session.js.map +1 -0
  199. package/dist/testing/fixtures.d.ts +27 -0
  200. package/dist/testing/fixtures.d.ts.map +1 -0
  201. package/dist/testing/fixtures.js +67 -0
  202. package/dist/testing/fixtures.js.map +1 -0
  203. package/dist/testing/index.d.ts +31 -0
  204. package/dist/testing/index.d.ts.map +1 -0
  205. package/dist/testing/index.js +31 -0
  206. package/dist/testing/index.js.map +1 -0
  207. package/dist/testing/mock-model.d.ts +43 -0
  208. package/dist/testing/mock-model.d.ts.map +1 -0
  209. package/dist/testing/mock-model.js +62 -0
  210. package/dist/testing/mock-model.js.map +1 -0
  211. package/dist/testing/node.d.ts +24 -0
  212. package/dist/testing/node.d.ts.map +1 -0
  213. package/dist/testing/node.js +24 -0
  214. package/dist/testing/node.js.map +1 -0
  215. package/dist/testing/vcr-global-setup.d.ts +3 -0
  216. package/dist/testing/vcr-global-setup.d.ts.map +1 -0
  217. package/dist/testing/vcr-global-setup.js +9 -0
  218. package/dist/testing/vcr-global-setup.js.map +1 -0
  219. package/dist/testing/vcr-shared.d.ts +21 -0
  220. package/dist/testing/vcr-shared.d.ts.map +1 -0
  221. package/dist/testing/vcr-shared.js +21 -0
  222. package/dist/testing/vcr-shared.js.map +1 -0
  223. package/dist/testing/vcr-spec.d.ts +17 -0
  224. package/dist/testing/vcr-spec.d.ts.map +1 -0
  225. package/dist/testing/vcr-spec.js +72 -0
  226. package/dist/testing/vcr-spec.js.map +1 -0
  227. package/dist/testing/vcr.d.ts +81 -0
  228. package/dist/testing/vcr.d.ts.map +1 -0
  229. package/dist/testing/vcr.js +211 -0
  230. package/dist/testing/vcr.js.map +1 -0
  231. package/dist/worker/index.d.ts +62 -0
  232. package/dist/worker/index.d.ts.map +1 -0
  233. package/dist/worker/index.js +194 -0
  234. package/dist/worker/index.js.map +1 -0
  235. package/eslint-rules/index.js +31 -0
  236. package/eslint-rules/no-deprecated-object-properties.js +81 -0
  237. package/package.json +147 -0
  238. package/scripts/generate-keys.mjs +53 -0
package/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # @loopingai/core
2
+
3
+ **The mandatory foundation for a Looping agent on Cloudflare Workers.**
4
+
5
+ Zero-trust A2A (signed AgentCard, gateway-JWT verification, no shared secrets), the
6
+ durable task lifecycle, the delegation and subagent runtime, and the test harness.
7
+
8
+ You bring the loop and the prompts. Core brings everything you cannot choose not to have.
9
+
10
+ ```bash
11
+ npm install @loopingai/core
12
+ ```
13
+
14
+ > Part of a three-package split:
15
+ > **`@loopingai/core`** (this) ·
16
+ > [`looping-plugins`](https://github.com/Looping-AI/looping-plugins) (optional, composable capabilities) ·
17
+ > [`looping-starter`](https://github.com/Looping-AI/looping-starter) (a working agent that composes them).
18
+
19
+ ---
20
+
21
+ ## Why this exists
22
+
23
+ An agent that talks to other agents has to answer one question before anything else:
24
+ _is the caller who they claim to be, and can they prove it without a shared secret?_
25
+ That answer — and the durable machinery for accepting a turn, decomposing it, and
26
+ delivering a result out of band — is identical for every agent. It is also the part
27
+ that is easy to get subtly and silently wrong.
28
+
29
+ So it ships once, here, with the security-critical paths pinned by tests. Anything
30
+ optional is a plugin. Anything opinionated belongs to your app.
31
+
32
+ ---
33
+
34
+ ## Quick start
35
+
36
+ ### 1. Generate a signing key
37
+
38
+ ```bash
39
+ npx looping-keys
40
+ ```
41
+
42
+ Set the private JWK as `A2A_SIGNING_KEY` (`.dev.vars` locally, `wrangler secret put`
43
+ when deployed) and the origins you accept calls from as `GATEWAY_ORIGINS`:
44
+
45
+ ```ini
46
+ # .dev.vars
47
+ A2A_SIGNING_KEY={"crv":"Ed25519","d":"…","x":"…","kty":"OKP","kid":"a2a-2026-08-01"}
48
+ GATEWAY_ORIGINS=["https://gateway.example.com"]
49
+ ```
50
+
51
+ The public half is never configured anywhere — the Worker derives it from the private
52
+ key and serves it at the card's `jku`.
53
+
54
+ ### 2. Put the A2A edge in front of your Durable Object
55
+
56
+ ```ts
57
+ import { createA2AWorker } from "@loopingai/core/worker";
58
+
59
+ const manifest = {
60
+ name: "my-agent",
61
+ description: "Does a useful thing.",
62
+ version: "1.0.0",
63
+ capabilities: { streaming: false, pushNotifications: true, extensions: [] },
64
+ defaultInputModes: ["text/plain"],
65
+ defaultOutputModes: ["text/plain"],
66
+ skills: []
67
+ };
68
+
69
+ export default {
70
+ fetch: createA2AWorker({
71
+ manifest,
72
+ // One DO instance per verified caller — this is what makes a task
73
+ // unreachable from any other caller by construction.
74
+ resolveAgent: (identity) =>
75
+ env.MY_AGENT.get(env.MY_AGENT.idFromName(identity.key!)),
76
+ // Must be idempotent: the gateway retries dispatch.
77
+ startTurn: async (turn) => {
78
+ await env.TURN_WORKFLOW.create({ id: turn.messageId, params: turn });
79
+ }
80
+ })
81
+ } satisfies ExportedHandler<Env>;
82
+ ```
83
+
84
+ That handler serves three routes: the public JWKS, a **signed** AgentCard at
85
+ `/.well-known/agent-card.json`, and gateway-authenticated JSON-RPC. Every POST is
86
+ verified before a Durable Object is ever addressed.
87
+
88
+ ### 3. Build the runtime in your Durable Object
89
+
90
+ Everything that would otherwise be a module-level constant is resolved once per DO
91
+ instance, from your config and your installed plugins:
92
+
93
+ ```ts
94
+ import { Agent } from "agents";
95
+ import { createAgentRuntime } from "@loopingai/core";
96
+ import { AgentDB } from "@loopingai/core/db";
97
+
98
+ export class MyAgent extends Agent<Env> {
99
+ runtime!: ReturnType<typeof createAgentRuntime>;
100
+ db!: AgentDB;
101
+
102
+ async onStart() {
103
+ this.runtime = createAgentRuntime({
104
+ config: { model: { chatModelId: "@cf/zai-org/glm-5.2" } },
105
+ plugins: [scraper({ apiKey: this.env.SCRAPER_API_KEY })],
106
+ env: this.env // opt in to verifying every plugin's declared bindings exist
107
+ });
108
+
109
+ this.db = new AgentDB(this.ctx.storage, {
110
+ maxSubtasks: this.runtime.config.maxSubtasks,
111
+ stores: this.runtime.stores
112
+ });
113
+ await this.db.ensureReady();
114
+ }
115
+ }
116
+ ```
117
+
118
+ Resolving a registry at _import_ time is the one thing this package exists to
119
+ prevent: it freezes the registry before `env` exists (which on Workers is always),
120
+ defeats tree-shaking, and makes runtime plugin selection impossible.
121
+
122
+ ---
123
+
124
+ ## Exports
125
+
126
+ No root barrel. Each area is its own subpath, so importing the delegation layer does
127
+ not drag in the A2A adapter, and the test harness cannot reach a production bundle.
128
+
129
+ | Subpath | What's in it |
130
+ | ------------------------------ | --------------------------------------------------------------------------- |
131
+ | `@loopingai/core` | `createAgentRuntime`, the plugin contract, config shapes, platform facts |
132
+ | `@loopingai/core/a2a` | card signing, JWKS, gateway-JWT verify, push notify, task store, executor |
133
+ | `@loopingai/core/worker` | `createA2AWorker()` — the whole zero-trust edge |
134
+ | `@loopingai/core/agent` | session, history, model runtime, inference, budget, control tools |
135
+ | `@loopingai/core/subtasks` | delegation types, decomposition, the `delegate` tool, wave scheduler |
136
+ | `@loopingai/core/subagent` | `RecipeSubagentBase`, resumable runs, fingerprinting, workspace |
137
+ | `@loopingai/core/db` | `AgentDB`, `notify_tasks` + `subtasks` schema, migrations, `PluginStore` |
138
+ | `@loopingai/core/testing` | VCR, `FakeSession`, `mockModel`, DO helpers, JWK fixtures — _workerd realm_ |
139
+ | `@loopingai/core/testing/node` | the VCR recorder — _Node realm, never import from a spec_ |
140
+ | `@loopingai/core/eslint` | the `no-deprecated-object-properties` rule |
141
+
142
+ `/testing*` and `/eslint` are structurally incapable of entering a runtime graph, and
143
+ `npm run verify:exports` asserts exactly that before every publish.
144
+
145
+ ---
146
+
147
+ ## The zero-trust model
148
+
149
+ No secret ever crosses the boundary, in either direction.
150
+
151
+ ```
152
+ Gateway ──── EdDSA JWT, jku → its public JWKS ────▶ Agent "the agent knows the gateway"
153
+ Agent ──── signed AgentCard, jku → its JWKS ────▶ Gateway "the gateway knows the agent"
154
+ ```
155
+
156
+ `verifyGatewayToken` runs four checks, in this order, on every single call:
157
+
158
+ 1. **`jku` present** in the protected header (RFC 7515 §4.1.2).
159
+ 2. **`jku` origin is allowlisted** — validated _before_ the fetch, so an attacker
160
+ cannot point `jku` at a JWKS they control.
161
+ 3. **`iss` origin equals `jku` origin** — one listed gateway cannot impersonate another.
162
+ 4. **`jwtVerify` pinned to EdDSA.**
163
+
164
+ All four are load-bearing. Do not make any of them optional, and do not add a
165
+ local-development bypass — run a local gateway instead. `verify.spec.ts` asserts each
166
+ one negatively, including that an unlisted `jku` is rejected _before_ any network
167
+ call happens.
168
+
169
+ The agent's card is signed over its **wire (protobuf-JSON) encoding**, which is what
170
+ makes the served document a fixed point under the repeated decoding a verifier
171
+ performs. A gateway pins the card's `kid` + `jku` on first registration
172
+ (Trust-On-First-Use).
173
+
174
+ ---
175
+
176
+ ## Plugins
177
+
178
+ A capability is a plugin. Core never imports one — your app registers it, which keeps
179
+ bundle size proportional to what you actually installed.
180
+
181
+ ```ts
182
+ import { definePlugin } from "@loopingai/core";
183
+
184
+ export const scraper = (config: { apiKey: string }) =>
185
+ definePlugin({
186
+ key: "scraper",
187
+ subtaskType: {
188
+ key: "scrape",
189
+ description: "fetch a page and summarize it",
190
+ params: z.object({ url: z.string().describe("page to fetch") }),
191
+ recipe
192
+ },
193
+ toolFamilies: { web: (ctx) => ({ tools: { fetchPage: /* … */ } }) },
194
+ capability: "You can scrape a page and summarize it.",
195
+ requires: { secrets: ["SCRAPER_API_KEY"] },
196
+ store: { plugin: "scraper", version: 1, ensureTables: (sql, from) => { /* … */ } }
197
+ });
198
+ ```
199
+
200
+ `createAgentRuntime` fails at DO start — never mid-request — on a duplicate plugin
201
+ key, a duplicate tool family, a missing declared binding, or a
202
+ `PLUGIN_CONTRACT_VERSION` mismatch. Because core, plugins, and starter publish from
203
+ separate repos, one of them is always briefly behind; that version assert turns the
204
+ skew into a readable sentence instead of a structural-type error several frames from
205
+ its cause.
206
+
207
+ The contract is **additive-only within a major**: new capabilities arrive as optional
208
+ fields on `AgentPlugin`.
209
+
210
+ ---
211
+
212
+ ## Testing
213
+
214
+ The harness both predecessor agents grew, shipped so you don't grow it a third time.
215
+
216
+ ```ts
217
+ import {
218
+ FakeSession,
219
+ mockModel,
220
+ makeGatewayToken,
221
+ makeDoHelpers
222
+ } from "@loopingai/core/testing";
223
+
224
+ const { withDb } = makeDoHelpers(env.MY_AGENT);
225
+
226
+ await withDb("accepts a turn once", async (db) => {
227
+ await db.ensureReady();
228
+ db.tasks.begin({ messageId: "m1", taskId: "t1", contextId: "c1" });
229
+ });
230
+ ```
231
+
232
+ - **VCR** — record/replay real HTTP against on-disk cassettes, split across the Node
233
+ and workerd realms because specs run in workerd, which has no filesystem. Point
234
+ vitest's `globalSetup` at `@loopingai/core/testing/vcr-global-setup`.
235
+ - **Fakes** — `FakeSession` (a `SessionLike` reference implementation) and `mockModel`
236
+ (a scripted `LanguageModel`), so a loop can be driven with no model call at all.
237
+ - **Fixtures** — Ed25519 keypairs and a gateway-JWT signer, so the zero-trust path is
238
+ exercisable end to end without a real gateway.
239
+
240
+ ---
241
+
242
+ ## What core deliberately does _not_ contain
243
+
244
+ - The turn loop, triage, and the DO / Workflow class bodies. Core ships the argument
245
+ and budget types; you write the loop.
246
+ - The main agent's soul. Core ships no prompt copy.
247
+ - Config _values_ — model ids, budgets, limits. Core ships the shapes and safe
248
+ defaults, and `resolveConfig` validates your overrides.
249
+ - Vectorize recall, browser tools, shell. All optional → plugins.
250
+
251
+ ---
252
+
253
+ ## Requirements
254
+
255
+ - **Node** ≥ 24 (for build and test only — the package itself runs on workerd)
256
+ - **Bindings:** `AI`, one Durable Object, one Workflow
257
+ - **Secrets:** `A2A_SIGNING_KEY`, `GATEWAY_ORIGINS`
258
+ - **Peers, never bundled:** `agents`, `ai`, `workers-ai-provider`
259
+
260
+ That last point is not stylistic: two copies of `agents` in one Worker breaks the
261
+ `Session` / `SessionMessage` types and every `instanceof`. For local development
262
+ across the three repos use `file:` overrides, or `npm pack` plus a tarball install —
263
+ **not `npm link`**, which duplicates peer dependencies.
264
+
265
+ ---
266
+
267
+ ## Contributing
268
+
269
+ [`AGENTS.md`](./AGENTS.md) documents the constraints this package is guardian of.
270
+
271
+ ```bash
272
+ npm run check # prettier + eslint + tsc (src) + tsc (test) + build
273
+ npm test # vitest, inside real workerd
274
+ npm run verify:exports # the publish gate: subpaths, ESM specifiers, realm isolation
275
+ ```
276
+
277
+ ## License
278
+
279
+ [GPL-3.0-only](./LICENSE).
@@ -0,0 +1,61 @@
1
+ import type { Task, TaskState } from "@a2a-js/sdk";
2
+ import type { GatewayIdentity } from "./verify.js";
3
+ import type { PlainTask } from "./task.js";
4
+ /**
5
+ * The task-lifecycle surface core calls on an agent Durable Object, declared
6
+ * structurally so core never imports a consumer's DO class.
7
+ *
8
+ * A consumer's `DurableObjectStub<MyAgent>` satisfies this by construction:
9
+ * Cloudflare's RPC type mapping wraps each return in `Promise<Serializable<T>>`,
10
+ * and every type below is already `Serializable` — that is precisely why
11
+ * {@link PlainTask} exists (see {@link file://./task.ts}).
12
+ *
13
+ * `listTasks` is optional. `ListTasks` is new in A2A v1.0 and an agent that does
14
+ * not keep a queryable task history simply omits it; {@link DurableTaskStore}
15
+ * then answers the RPC with `UnsupportedOperationError` instead of failing at
16
+ * the binding. Everything else is mandatory — they are the accept-and-notify
17
+ * contract.
18
+ */
19
+ export interface TaskAgent {
20
+ /**
21
+ * Record (or reuse) the `submitted` task for a turn. **Must be idempotent on
22
+ * `messageId`**: the gateway retries dispatch, and the executor relies on this
23
+ * plus a deterministic workflow id to make a turn run exactly once.
24
+ */
25
+ beginTask(input: {
26
+ messageId: string;
27
+ taskId: string;
28
+ contextId: string;
29
+ }): Promise<PlainTask>;
30
+ getTask(taskId: string): Promise<PlainTask | null>;
31
+ /** Returns false when the write was refused (e.g. the task is already terminal). */
32
+ saveTask(task: Task): Promise<boolean>;
33
+ /** Marks the task canceled and returns it, or null when there is no such task. */
34
+ cancelTask(taskId: string): Promise<PlainTask | null>;
35
+ listTasks?(query: TaskListQuery): Promise<TaskListPage>;
36
+ }
37
+ export interface TaskListQuery {
38
+ contextId?: string;
39
+ state?: TaskState;
40
+ /** Epoch ms; matches the `updated_at` column stamped on every status write. */
41
+ updatedAfter?: number;
42
+ includeArtifacts: boolean;
43
+ historyLength?: number;
44
+ limit: number;
45
+ offset: number;
46
+ }
47
+ export interface TaskListPage {
48
+ tasks: Task[];
49
+ /** Total matching rows, ignoring `limit`/`offset` — drives `nextPageToken`. */
50
+ totalSize: number;
51
+ }
52
+ /**
53
+ * Resolve the agent DO stub for a verified caller.
54
+ *
55
+ * Supplied by the consumer because addressing is theirs: core does not know the
56
+ * binding name, and the identity→instance mapping is a product decision. The
57
+ * conventional implementation keys one DO instance per `identity.key`, which is
58
+ * what makes a task unreachable from any other caller by construction.
59
+ */
60
+ export type AgentResolver = (identity: GatewayIdentity) => TaskAgent;
61
+ //# sourceMappingURL=agent-stub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-stub.d.ts","sourceRoot":"","sources":["../../src/a2a/agent-stub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAE3C;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,SAAS;IACxB;;;;OAIG;IACH,SAAS,CAAC,KAAK,EAAE;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAEvB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAEnD,oFAAoF;IACpF,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvC,kFAAkF;IAClF,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IAEtD,SAAS,CAAC,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,QAAQ,EAAE,eAAe,KAAK,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=agent-stub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-stub.js","sourceRoot":"","sources":["../../src/a2a/agent-stub.ts"],"names":[],"mappings":""}
@@ -0,0 +1,119 @@
1
+ import type { JWK } from "jose";
2
+ import { AgentCard } from "@a2a-js/sdk";
3
+ /** The default JSON-RPC path an agent answers on (the card's only interface). */
4
+ export declare const A2A_RPC_PATH = "/a2a";
5
+ /**
6
+ * The transport-independent half of an {@link AgentCard} — everything that does
7
+ * not depend on the request origin. {@link buildBaseCard} adds
8
+ * `supportedInterfaces` and the security fields.
9
+ *
10
+ * Derived from the SDK `AgentCard` rather than hand-declared so a protocol field
11
+ * that gains a requirement fails the build here instead of silently going
12
+ * unadvertised.
13
+ */
14
+ export type AgentManifest = Pick<AgentCard, "name" | "description" | "version" | "capabilities" | "defaultInputModes" | "defaultOutputModes" | "skills">;
15
+ /**
16
+ * An AgentCard in its **protobuf-JSON** encoding — the form served at the
17
+ * well-known path and the exact document a signature is computed over. Distinct
18
+ * from the in-memory `AgentCard`: the wire form omits proto defaults and renders
19
+ * oneofs (`securitySchemes`, `Part.content`) as a single key rather than a
20
+ * `$case` tag.
21
+ */
22
+ export type WireAgentCard = Record<string, unknown>;
23
+ export interface CardSigningConfig {
24
+ /** Ed25519 private JWK (with `kid`) that signs the card. */
25
+ privateJwk: JWK & {
26
+ kid: string;
27
+ };
28
+ /** Public URL serving this agent's JWKS — embedded as the JWS `jku`. */
29
+ jku: string;
30
+ }
31
+ export interface BuildCardOptions {
32
+ /** Origin the card is served from; the interface `url` is built under it. */
33
+ origin: string;
34
+ /** Path this agent answers JSON-RPC on. Defaults to {@link A2A_RPC_PATH}. */
35
+ rpcPath?: string;
36
+ /**
37
+ * Advertise the gateway's Bearer-JWT scheme in `securitySchemes`.
38
+ *
39
+ * **Defaults to `false` for historical reasons that no longer hold — see
40
+ * below before relying on the default.**
41
+ *
42
+ * `SecurityScheme` is the card's only protobuf *oneof*, and in earlier SDK
43
+ * releases `SecurityScheme.fromJSON` read only the wire spelling
44
+ * (`{ httpAuthSecurityScheme: … }`) and not the decoded `$case` form. A
45
+ * verifier that decodes the fetched card and then canonicalizes it through
46
+ * `toJSON(fromJSON(card))` decodes twice, and the second pass collapsed
47
+ * `{ gatewayJwt: { httpAuthSecurityScheme: … } }` to `{ gatewayJwt: {} }` — a
48
+ * different document from the one that was signed, so the signature failed.
49
+ * looping-gateway's `canonicalCardPayload` double-decodes exactly this way.
50
+ *
51
+ * **As of the pinned `@a2a-js/sdk` (1.0.1) that is fixed.** `card.spec.ts`
52
+ * asserts it directly: an advertised-schemes card is a fixed point under
53
+ * repeated decoding, and its signature verifies both as served and after a
54
+ * double decode. So the safety argument for the `false` default is gone, and
55
+ * the remaining reason to keep it is deployment ordering — a gateway pinned to
56
+ * an older SDK copy would still collapse the oneof. Flip the default once the
57
+ * gateways in play are known to be on ≥1.0.1; the specs will hold the line if
58
+ * a later SDK regresses.
59
+ *
60
+ * `securityRequirements` is unaffected either way — it is a plain map, not a
61
+ * oneof — so the card always declares that auth is *required*, it just may not
62
+ * describe the scheme.
63
+ */
64
+ advertiseSecuritySchemes?: boolean;
65
+ }
66
+ /**
67
+ * Build the (unsigned) AgentCard.
68
+ *
69
+ * v1.0 replaced the card's flat `url` / `preferredTransport` / `protocolVersion`
70
+ * fields with an ordered `supportedInterfaces` list, where each entry pins its
71
+ * own protocol binding *and* protocol version. That per-interface
72
+ * `protocolVersion` is what a client's transport factory matches on and what the
73
+ * Worker validates the `A2A-Version` request header against, so it must be the
74
+ * real protocol version rather than the agent's own version string.
75
+ */
76
+ export declare function buildBaseCard(manifest: AgentManifest, opts: BuildCardOptions): AgentCard;
77
+ /**
78
+ * Sign the card with a detached-payload EdDSA flattened JWS, returning the
79
+ * signed **wire** card to serve. v1.0 standardized this (A2A spec §8.4), so the
80
+ * canonicalization and JWS construction are the SDK's rather than a local
81
+ * scheme: a JCS (RFC 8785) canonicalization of the card with `signatures`
82
+ * removed, under a protected header carrying `alg`, `kid` and `typ` (all three
83
+ * are required — the SDK verifier rejects a signature missing any of them).
84
+ *
85
+ * The card is normalized to its wire form *before* signing because that is what
86
+ * the verifier canonicalizes: `verifyAgentCardSignature` re-encodes whatever
87
+ * document it fetched through `AgentCard.toJSON(AgentCard.fromJSON(card))`.
88
+ * Signing the in-memory proto object instead would canonicalize `$case` tags and
89
+ * proto defaults that never appear on the wire, and every signature would fail.
90
+ *
91
+ * A verifying gateway strips the `signatures` array, recomputes the canonical
92
+ * payload, and verifies — pinning this key's `kid`+`jku` on first registration
93
+ * (Trust-On-First-Use).
94
+ */
95
+ export declare function signCard(card: AgentCard, cfg: CardSigningConfig): Promise<WireAgentCard>;
96
+ /**
97
+ * The card's protobuf-JSON encoding. Typed back as `AgentCard` for the SDK
98
+ * signer, which is generic over "the document to canonicalize" rather than over
99
+ * the in-memory shape specifically.
100
+ *
101
+ * Encode-only — never `toJSON(fromJSON(card))`. `fromJSON` reads the *wire*
102
+ * spelling of a oneof, so running it over an in-memory card silently drops every
103
+ * `$case`-tagged field. The verifier's round trip is the mirror image and stable:
104
+ * it decodes the document it fetched and re-encodes it to exactly this.
105
+ */
106
+ export declare function wireCard(card: AgentCard): AgentCard;
107
+ /**
108
+ * Parse and validate the `A2A_SIGNING_KEY` secret into the private JWK used to
109
+ * sign the card. Throws if the JWK is missing its `kid` (required for the JWS
110
+ * protected header and gateway key-pinning).
111
+ */
112
+ export declare function parsePrivateJwk(raw: string): CardSigningConfig["privateJwk"];
113
+ /** Public card-signing JWKS (served at the `jku`): the private JWK minus `d`. */
114
+ export declare function publicCardJwks(privateJwk: JWK & {
115
+ kid: string;
116
+ }): {
117
+ keys: JWK[];
118
+ };
119
+ //# sourceMappingURL=card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../src/a2a/card.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,MAAM,CAAC;AAChC,OAAO,EAEL,SAAS,EAEV,MAAM,aAAa,CAAC;AAKrB,iFAAiF;AACjF,eAAO,MAAM,YAAY,SAAS,CAAC;AAEnC;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,SAAS,EACP,MAAM,GACN,aAAa,GACb,SAAS,GACT,cAAc,GACd,mBAAmB,GACnB,oBAAoB,GACpB,QAAQ,CACX,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,UAAU,EAAE,GAAG,GAAG;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACpC;AAUD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,gBAAgB,GACrB,SAAS,CAiCX;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,EACf,GAAG,EAAE,iBAAiB,GACrB,OAAO,CAAC,aAAa,CAAC,CASxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,SAAS,CAEnD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAI5E;AAED,iFAAiF;AACjF,wBAAgB,cAAc,CAAC,UAAU,EAAE,GAAG,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IACjE,IAAI,EAAE,GAAG,EAAE,CAAC;CACb,CAIA"}
@@ -0,0 +1,115 @@
1
+ import { A2A_PROTOCOL_VERSION, AgentCard, generateAgentCardSignature } from "@a2a-js/sdk";
2
+ /** JWS algorithm for the card signature — must match the gateway (`EdDSA`). */
3
+ const ALG = "EdDSA";
4
+ /** The default JSON-RPC path an agent answers on (the card's only interface). */
5
+ export const A2A_RPC_PATH = "/a2a";
6
+ /**
7
+ * The `httpAuthSecurityScheme` describing the gateway's Bearer JWT, and the
8
+ * requirement that references it. Split out so the requirement can be advertised
9
+ * even when {@link BuildCardOptions.advertiseSecuritySchemes} keeps the scheme
10
+ * itself off the wire.
11
+ */
12
+ const GATEWAY_SCHEME_ID = "gatewayJwt";
13
+ /**
14
+ * Build the (unsigned) AgentCard.
15
+ *
16
+ * v1.0 replaced the card's flat `url` / `preferredTransport` / `protocolVersion`
17
+ * fields with an ordered `supportedInterfaces` list, where each entry pins its
18
+ * own protocol binding *and* protocol version. That per-interface
19
+ * `protocolVersion` is what a client's transport factory matches on and what the
20
+ * Worker validates the `A2A-Version` request header against, so it must be the
21
+ * real protocol version rather than the agent's own version string.
22
+ */
23
+ export function buildBaseCard(manifest, opts) {
24
+ const rpcPath = opts.rpcPath ?? A2A_RPC_PATH;
25
+ return {
26
+ ...manifest,
27
+ supportedInterfaces: [
28
+ {
29
+ url: `${opts.origin}${rpcPath}`,
30
+ protocolBinding: "JSONRPC",
31
+ protocolVersion: A2A_PROTOCOL_VERSION,
32
+ // Single-tenant agent: one DO per verified gateway identity, so the
33
+ // protocol-level tenant slot goes unused.
34
+ tenant: ""
35
+ }
36
+ ],
37
+ provider: undefined,
38
+ documentationUrl: undefined,
39
+ iconUrl: undefined,
40
+ securitySchemes: opts.advertiseSecuritySchemes
41
+ ? {
42
+ [GATEWAY_SCHEME_ID]: {
43
+ scheme: {
44
+ $case: "httpAuthSecurityScheme",
45
+ value: { description: "", scheme: "bearer", bearerFormat: "JWT" }
46
+ }
47
+ }
48
+ }
49
+ : {},
50
+ // v0.3's `security: [{ gatewayJwt: [] }]`. The empty `list` means the scheme
51
+ // is required but carries no scopes. Safe to advertise unconditionally: this
52
+ // is a plain map, so it survives the round trip that eats `securitySchemes`.
53
+ securityRequirements: [{ schemes: { [GATEWAY_SCHEME_ID]: { list: [] } } }],
54
+ signatures: []
55
+ };
56
+ }
57
+ /**
58
+ * Sign the card with a detached-payload EdDSA flattened JWS, returning the
59
+ * signed **wire** card to serve. v1.0 standardized this (A2A spec §8.4), so the
60
+ * canonicalization and JWS construction are the SDK's rather than a local
61
+ * scheme: a JCS (RFC 8785) canonicalization of the card with `signatures`
62
+ * removed, under a protected header carrying `alg`, `kid` and `typ` (all three
63
+ * are required — the SDK verifier rejects a signature missing any of them).
64
+ *
65
+ * The card is normalized to its wire form *before* signing because that is what
66
+ * the verifier canonicalizes: `verifyAgentCardSignature` re-encodes whatever
67
+ * document it fetched through `AgentCard.toJSON(AgentCard.fromJSON(card))`.
68
+ * Signing the in-memory proto object instead would canonicalize `$case` tags and
69
+ * proto defaults that never appear on the wire, and every signature would fail.
70
+ *
71
+ * A verifying gateway strips the `signatures` array, recomputes the canonical
72
+ * payload, and verifies — pinning this key's `kid`+`jku` on first registration
73
+ * (Trust-On-First-Use).
74
+ */
75
+ export async function signCard(card, cfg) {
76
+ const sign = generateAgentCardSignature(cfg.privateJwk, {
77
+ alg: ALG,
78
+ kid: cfg.privateJwk.kid,
79
+ typ: "JOSE",
80
+ jku: cfg.jku
81
+ });
82
+ const signed = await sign(wireCard(card));
83
+ return signed;
84
+ }
85
+ /**
86
+ * The card's protobuf-JSON encoding. Typed back as `AgentCard` for the SDK
87
+ * signer, which is generic over "the document to canonicalize" rather than over
88
+ * the in-memory shape specifically.
89
+ *
90
+ * Encode-only — never `toJSON(fromJSON(card))`. `fromJSON` reads the *wire*
91
+ * spelling of a oneof, so running it over an in-memory card silently drops every
92
+ * `$case`-tagged field. The verifier's round trip is the mirror image and stable:
93
+ * it decodes the document it fetched and re-encodes it to exactly this.
94
+ */
95
+ export function wireCard(card) {
96
+ return AgentCard.toJSON(card);
97
+ }
98
+ /**
99
+ * Parse and validate the `A2A_SIGNING_KEY` secret into the private JWK used to
100
+ * sign the card. Throws if the JWK is missing its `kid` (required for the JWS
101
+ * protected header and gateway key-pinning).
102
+ */
103
+ export function parsePrivateJwk(raw) {
104
+ const jwk = JSON.parse(raw);
105
+ if (!jwk.kid)
106
+ throw new Error("A2A_SIGNING_KEY must include a `kid`");
107
+ return jwk;
108
+ }
109
+ /** Public card-signing JWKS (served at the `jku`): the private JWK minus `d`. */
110
+ export function publicCardJwks(privateJwk) {
111
+ const { d: _d, ...pub } = privateJwk;
112
+ void _d;
113
+ return { keys: [{ ...pub, use: "sig", alg: ALG }] };
114
+ }
115
+ //# sourceMappingURL=card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card.js","sourceRoot":"","sources":["../../src/a2a/card.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,SAAS,EACT,0BAA0B,EAC3B,MAAM,aAAa,CAAC;AAErB,+EAA+E;AAC/E,MAAM,GAAG,GAAG,OAAO,CAAC;AAEpB,iFAAiF;AACjF,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC;AA0EnC;;;;;GAKG;AACH,MAAM,iBAAiB,GAAG,YAAY,CAAC;AAEvC;;;;;;;;;GASG;AACH,MAAM,UAAU,aAAa,CAC3B,QAAuB,EACvB,IAAsB;IAEtB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,YAAY,CAAC;IAC7C,OAAO;QACL,GAAG,QAAQ;QACX,mBAAmB,EAAE;YACnB;gBACE,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE;gBAC/B,eAAe,EAAE,SAAS;gBAC1B,eAAe,EAAE,oBAAoB;gBACrC,oEAAoE;gBACpE,0CAA0C;gBAC1C,MAAM,EAAE,EAAE;aACX;SACF;QACD,QAAQ,EAAE,SAAS;QACnB,gBAAgB,EAAE,SAAS;QAC3B,OAAO,EAAE,SAAS;QAClB,eAAe,EAAE,IAAI,CAAC,wBAAwB;YAC5C,CAAC,CAAC;gBACE,CAAC,iBAAiB,CAAC,EAAE;oBACnB,MAAM,EAAE;wBACN,KAAK,EAAE,wBAAwB;wBAC/B,KAAK,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE;qBAClE;iBACF;aACF;YACH,CAAC,CAAC,EAAE;QACN,6EAA6E;QAC7E,6EAA6E;QAC7E,6EAA6E;QAC7E,oBAAoB,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC;QAC1E,UAAU,EAAE,EAAE;KACf,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAe,EACf,GAAsB;IAEtB,MAAM,IAAI,GAAG,0BAA0B,CAAC,GAAG,CAAC,UAAU,EAAE;QACtD,GAAG,EAAE,GAAG;QACR,GAAG,EAAE,GAAG,CAAC,UAAU,CAAC,GAAG;QACvB,GAAG,EAAE,MAAM;QACX,GAAG,EAAE,GAAG,CAAC,GAAG;KACb,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1C,OAAO,MAAkC,CAAC;AAC5C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAe;IACtC,OAAO,SAAS,CAAC,MAAM,CAAC,IAAI,CAAc,CAAC;AAC7C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAqB,CAAC;IAChD,IAAI,CAAC,GAAG,CAAC,GAAG;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IACtE,OAAO,GAAsC,CAAC;AAChD,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,cAAc,CAAC,UAAiC;IAG9D,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,GAAG,EAAE,GAAG,UAAU,CAAC;IACrC,KAAK,EAAE,CAAC;IACR,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;AACtD,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { type ServerCallContext } from "@a2a-js/sdk/server";
2
+ import type { GatewayIdentity } 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` is deliberately unset: one agent DO instance *is* the tenant, keyed
9
+ * by the verified caller, so there is no second axis to scope on.
10
+ */
11
+ export declare function buildCallContext(request: Request, identity: GatewayIdentity): ServerCallContext;
12
+ /** Echo back the extensions the handler actually activated (spec §14.2.2). */
13
+ export declare function extensionHeaders(context: ServerCallContext): HeadersInit;
14
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/a2a/context.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,iBAAiB,EAEvB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA6BnD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,eAAe,GACxB,iBAAiB,CAYnB;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,WAAW,CAIxE"}
@@ -0,0 +1,55 @@
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 gateway-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 GatewayUser {
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` is deliberately unset: one agent DO instance *is* the tenant, keyed
35
+ * by the verified caller, so there is no second axis to scope on.
36
+ */
37
+ export function buildCallContext(request, identity) {
38
+ const headers = {};
39
+ for (const [name, value] of request.headers)
40
+ headers[name] = value;
41
+ return defaultServerCallContextBuilder({
42
+ extensions: Extensions.parseServiceParameter(request.headers.get(HTTP_EXTENSION_HEADER) ?? undefined),
43
+ user: new GatewayUser(identity),
44
+ headers,
45
+ requestedVersion: request.headers.get(A2A_VERSION_HEADER) ?? undefined
46
+ });
47
+ }
48
+ /** Echo back the extensions the handler actually activated (spec §14.2.2). */
49
+ export function extensionHeaders(context) {
50
+ const activated = context.activatedExtensions;
51
+ if (!activated?.length)
52
+ return {};
53
+ return { [HTTP_EXTENSION_HEADER]: Extensions.toServiceParameter(activated) };
54
+ }
55
+ //# sourceMappingURL=context.js.map