@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/a2a/context.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,qBAAqB,EACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,+BAA+B,EAIhC,MAAM,oBAAoB,CAAC;AAG5B;;;;;;GAMG;AAEH;;;;;GAKG;AACH,MAAM,WAAW;IACc;IAA7B,YAA6B,QAAyB;QAAzB,aAAQ,GAAR,QAAQ,CAAiB;IAAG,CAAC;IAE1D,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,QAAQ;QACV,yEAAyE;QACzE,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,EAAE,CAAC;IACjC,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAgB,EAChB,QAAyB;IAEzB,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,OAAO,CAAC,OAAO;QAAE,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IAEnE,OAAO,+BAA+B,CAAC;QACrC,UAAU,EAAE,UAAU,CAAC,qBAAqB,CAC1C,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,IAAI,SAAS,CACxD;QACD,IAAI,EAAE,IAAI,WAAW,CAAC,QAAQ,CAAC;QAC/B,OAAO;QACP,gBAAgB,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,IAAI,SAAS;KACvE,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,gBAAgB,CAAC,OAA0B;IACzD,MAAM,SAAS,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAC9C,IAAI,CAAC,SAAS,EAAE,MAAM;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,EAAE,CAAC,qBAAqB,CAAC,EAAE,UAAU,CAAC,kBAAkB,CAAC,SAAS,CAAC,EAAE,CAAC;AAC/E,CAAC"}
@@ -0,0 +1,85 @@
1
+ import { type AgentExecutor, type ExecutionEventBus, type RequestContext } from "@a2a-js/sdk/server";
2
+ import type { GatewayIdentity } from "./verify.js";
3
+ import type { AgentResolver } from "./agent-stub.js";
4
+ /**
5
+ * Derive a deterministic workflow instance id for a turn. Keyed on the gateway's
6
+ * `messageId` (stable across dispatch retries), so re-creating it is a no-op —
7
+ * the turn runs exactly once. Sanitized to the id charset.
8
+ */
9
+ export declare function workflowIdForMessage(messageId: string, prefix?: string): string;
10
+ /**
11
+ * Everything the executor knows about an accepted turn, handed to the
12
+ * consumer's {@link TurnStarter}.
13
+ *
14
+ * Core stops here deliberately: which workflow runs the turn, what else its
15
+ * params carry, and which binding it is created on are all the agent's, so core
16
+ * describes the turn and the agent starts it.
17
+ */
18
+ export interface AcceptedTurn {
19
+ /** The gateway's message id — the idempotency key for the whole turn. */
20
+ messageId: string;
21
+ taskId: string;
22
+ contextId: string;
23
+ /** The caller's message, flattened to text. */
24
+ text: string;
25
+ identity: GatewayIdentity;
26
+ /** Webhook the terminal task is POSTed to. */
27
+ pushUrl: string;
28
+ /** Per-task validation token echoed on the callback. */
29
+ pushToken: string;
30
+ /** This agent's card-signing JWKS URL — the callback JWT `jku`. */
31
+ jku: string;
32
+ }
33
+ /**
34
+ * Start the durable turn. **Must be idempotent**: a dispatch retry calls it
35
+ * again with the same {@link AcceptedTurn.messageId}, and the conventional
36
+ * implementation swallows the workflow's "instance already exists" race — see
37
+ * {@link workflowIdForMessage} and {@link ignoreAlreadyExists}.
38
+ */
39
+ export type TurnStarter = (turn: AcceptedTurn) => Promise<void>;
40
+ export interface ExecutorConfig {
41
+ identity: GatewayIdentity;
42
+ /** This agent's card-signing JWKS URL — the callback JWT `jku`. */
43
+ jku: string;
44
+ resolveAgent: AgentResolver;
45
+ startTurn: TurnStarter;
46
+ }
47
+ /**
48
+ * Run `create` and swallow the "instance already exists" retry race, so a
49
+ * {@link TurnStarter} is idempotent in the one way that actually happens.
50
+ *
51
+ * ```ts
52
+ * startTurn: (turn) =>
53
+ * ignoreAlreadyExists(() =>
54
+ * env.HANDLE_TASK_WORKFLOW.create({
55
+ * id: workflowIdForMessage(turn.messageId),
56
+ * params: { ...turn }
57
+ * })
58
+ * )
59
+ * ```
60
+ */
61
+ export declare function ignoreAlreadyExists(create: () => Promise<unknown>): Promise<void>;
62
+ /**
63
+ * A2A executor for the **async accept + notify** contract. On `SendMessage` it
64
+ * does not block on generation: it records a `submitted` task in the caller's DO
65
+ * (idempotent on `messageId`), hands the turn to a durable workflow, and
66
+ * publishes the accepted task immediately as the response. The workflow
67
+ * generates the reply and POSTs it to the gateway's push-notification webhook
68
+ * out of band.
69
+ *
70
+ * The verified caller identity comes from the config — the outer Worker builds
71
+ * one executor per verified request. The push config comes from the request
72
+ * itself: v1.0 hands the executor the whole `SendMessageRequest` via
73
+ * {@link RequestContext.request}, so nothing has to be threaded around it.
74
+ */
75
+ export declare class A2AExecutor implements AgentExecutor {
76
+ private readonly config;
77
+ constructor(config: ExecutorConfig);
78
+ execute: (requestContext: RequestContext, eventBus: ExecutionEventBus) => Promise<void>;
79
+ /**
80
+ * `CancelTask`: best-effort mark the task canceled in the DO and publish the
81
+ * canceled task. The in-flight workflow's `notify` step skips a canceled task.
82
+ */
83
+ cancelTask: (taskId: string, eventBus: ExecutionEventBus) => Promise<void>;
84
+ }
85
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/a2a/executor.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,MAAM,SAAS,GACd,MAAM,CAER;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,eAAe,CAAC;IAC1B,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,CAAC;IAC1B,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,aAAa,CAAC;IAC5B,SAAS,EAAE,WAAW,CAAC;CACxB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAC7B,OAAO,CAAC,IAAI,CAAC,CASf;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,WAAY,YAAW,aAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,cAAc;IAEnD,OAAO,GACL,gBAAgB,cAAc,EAC9B,UAAU,iBAAiB,KAC1B,OAAO,CAAC,IAAI,CAAC,CA6Cd;IAEF;;;OAGG;IACH,UAAU,GACR,QAAQ,MAAM,EACd,UAAU,iBAAiB,KAC1B,OAAO,CAAC,IAAI,CAAC,CAMd;CACH"}
@@ -0,0 +1,107 @@
1
+ import { AgentEvent } from "@a2a-js/sdk/server";
2
+ import { textOf } from "./parts.js";
3
+ /**
4
+ * Derive a deterministic workflow instance id for a turn. Keyed on the gateway's
5
+ * `messageId` (stable across dispatch retries), so re-creating it is a no-op —
6
+ * the turn runs exactly once. Sanitized to the id charset.
7
+ */
8
+ export function workflowIdForMessage(messageId, prefix = "turn") {
9
+ return `${prefix}-${messageId.replace(/[^A-Za-z0-9_-]/g, "-")}`;
10
+ }
11
+ /**
12
+ * Run `create` and swallow the "instance already exists" retry race, so a
13
+ * {@link TurnStarter} is idempotent in the one way that actually happens.
14
+ *
15
+ * ```ts
16
+ * startTurn: (turn) =>
17
+ * ignoreAlreadyExists(() =>
18
+ * env.HANDLE_TASK_WORKFLOW.create({
19
+ * id: workflowIdForMessage(turn.messageId),
20
+ * params: { ...turn }
21
+ * })
22
+ * )
23
+ * ```
24
+ */
25
+ export async function ignoreAlreadyExists(create) {
26
+ try {
27
+ await create();
28
+ }
29
+ catch (err) {
30
+ if (err instanceof Error && /already exists|exist/i.test(err.message)) {
31
+ return;
32
+ }
33
+ throw err;
34
+ }
35
+ }
36
+ /**
37
+ * A2A executor for the **async accept + notify** contract. On `SendMessage` it
38
+ * does not block on generation: it records a `submitted` task in the caller's DO
39
+ * (idempotent on `messageId`), hands the turn to a durable workflow, and
40
+ * publishes the accepted task immediately as the response. The workflow
41
+ * generates the reply and POSTs it to the gateway's push-notification webhook
42
+ * out of band.
43
+ *
44
+ * The verified caller identity comes from the config — the outer Worker builds
45
+ * one executor per verified request. The push config comes from the request
46
+ * itself: v1.0 hands the executor the whole `SendMessageRequest` via
47
+ * {@link RequestContext.request}, so nothing has to be threaded around it.
48
+ */
49
+ export class A2AExecutor {
50
+ config;
51
+ constructor(config) {
52
+ this.config = config;
53
+ }
54
+ execute = async (requestContext, eventBus) => {
55
+ const pushConfig = requestContext.request.configuration?.taskPushNotificationConfig;
56
+ // Defensive: the Worker validates url + token before the executor runs, and
57
+ // must keep doing so — a throw here is turned into a `failed` task by the
58
+ // request handler, not into the JSON-RPC error the caller needs to see.
59
+ if (!pushConfig?.url || !pushConfig.token) {
60
+ throw new Error("taskPushNotificationConfig url and token are required");
61
+ }
62
+ const text = textOf(requestContext.userMessage);
63
+ const messageId = requestContext.userMessage.messageId;
64
+ const contextId = requestContext.contextId;
65
+ // `identity.key` is guaranteed non-null: the Worker rejects a keyless
66
+ // identity (400) before constructing this executor.
67
+ const stub = this.config.resolveAgent(this.config.identity);
68
+ // Record (or reuse) the submitted task, then start the durable turn. Both
69
+ // are idempotent, so a dispatch retry heals a crash between the two.
70
+ const accepted = await stub.beginTask({
71
+ messageId,
72
+ taskId: requestContext.taskId,
73
+ contextId
74
+ });
75
+ // Widened in one explicit step: DO-stub returns come back through
76
+ // Cloudflare's RPC type mapping, and letting that mapped type flow into a
77
+ // generic SDK call site instead exceeds TypeScript's instantiation depth on
78
+ // the v1.0 (proto-generated) model.
79
+ const task = accepted;
80
+ await this.config.startTurn({
81
+ messageId,
82
+ taskId: accepted.id,
83
+ contextId,
84
+ text,
85
+ identity: this.config.identity,
86
+ pushUrl: pushConfig.url,
87
+ pushToken: pushConfig.token,
88
+ jku: this.config.jku
89
+ });
90
+ // The accept ack: a `submitted` task, not a Message. Returned synchronously.
91
+ eventBus.publish(AgentEvent.task(task));
92
+ eventBus.finished();
93
+ };
94
+ /**
95
+ * `CancelTask`: best-effort mark the task canceled in the DO and publish the
96
+ * canceled task. The in-flight workflow's `notify` step skips a canceled task.
97
+ */
98
+ cancelTask = async (taskId, eventBus) => {
99
+ const task = await this.config
100
+ .resolveAgent(this.config.identity)
101
+ .cancelTask(taskId);
102
+ if (task)
103
+ eventBus.publish(AgentEvent.task(task));
104
+ eventBus.finished();
105
+ };
106
+ }
107
+ //# sourceMappingURL=executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.js","sourceRoot":"","sources":["../../src/a2a/executor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * `@loopingai/core/a2a` — the A2A protocol adapter.
3
+ *
4
+ * The zero-trust contract in both directions (verify the gateway with its public
5
+ * JWKS, prove ourselves with ours), the accept-and-notify task lifecycle, and the
6
+ * narrowed task types that survive Durable Object RPC. Nothing here knows what an
7
+ * agent *does* — past {@link A2AExecutor} everything is plain strings.
8
+ */
9
+ export { IDENTITY_CLAIM, GatewayAuthError, bearerToken, normalizeGatewayOrigins, verifyGatewayToken, type GatewayIdentity, type VerifyOptions } from "./verify.js";
10
+ export { A2A_RPC_PATH, buildBaseCard, signCard, wireCard, parsePrivateJwk, publicCardJwks, type AgentManifest, type BuildCardOptions, type CardSigningConfig, type WireAgentCard } from "./card.js";
11
+ export { NOTIFICATION_TOKEN_HEADER, buildSubmittedTask, buildWorkingTask, buildCompletedTask, buildFailedTask, buildNoReplyCompletedTask, signCallbackJwt, postNotification } from "./notify.js";
12
+ export { taskStateLabel, type PlainArtifact, type PlainMessage, type PlainPart, type PlainStatus, type PlainTask } from "./task.js";
13
+ export { textPart, partsText, textOf, agentTextMessage, inboundText, InboundPartError, MAX_INBOUND_TEXT_BYTES } from "./parts.js";
14
+ export { buildCallContext, extensionHeaders } from "./context.js";
15
+ export { DurableTaskStore } from "./task-store.js";
16
+ export { A2AExecutor, workflowIdForMessage, ignoreAlreadyExists, type AcceptedTurn, type ExecutorConfig, type TurnStarter } from "./executor.js";
17
+ export type { AgentResolver, TaskAgent, TaskListPage, TaskListQuery } from "./agent-stub.js";
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/a2a/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,uBAAuB,EACvB,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EACnB,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,yBAAyB,EACzB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,yBAAyB,EACzB,eAAe,EACf,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,cAAc,EACd,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,SAAS,EACf,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,QAAQ,EACR,SAAS,EACT,MAAM,EACN,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,mBAAmB,EACnB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,WAAW,EACjB,MAAM,eAAe,CAAC;AAEvB,YAAY,EACV,aAAa,EACb,SAAS,EACT,YAAY,EACZ,aAAa,EACd,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `@loopingai/core/a2a` — the A2A protocol adapter.
3
+ *
4
+ * The zero-trust contract in both directions (verify the gateway with its public
5
+ * JWKS, prove ourselves with ours), the accept-and-notify task lifecycle, and the
6
+ * narrowed task types that survive Durable Object RPC. Nothing here knows what an
7
+ * agent *does* — past {@link A2AExecutor} everything is plain strings.
8
+ */
9
+ export { IDENTITY_CLAIM, GatewayAuthError, bearerToken, normalizeGatewayOrigins, verifyGatewayToken } from "./verify.js";
10
+ export { A2A_RPC_PATH, buildBaseCard, signCard, wireCard, parsePrivateJwk, publicCardJwks } from "./card.js";
11
+ export { NOTIFICATION_TOKEN_HEADER, buildSubmittedTask, buildWorkingTask, buildCompletedTask, buildFailedTask, buildNoReplyCompletedTask, signCallbackJwt, postNotification } from "./notify.js";
12
+ export { taskStateLabel } from "./task.js";
13
+ export { textPart, partsText, textOf, agentTextMessage, inboundText, InboundPartError, MAX_INBOUND_TEXT_BYTES } from "./parts.js";
14
+ export { buildCallContext, extensionHeaders } from "./context.js";
15
+ export { DurableTaskStore } from "./task-store.js";
16
+ export { A2AExecutor, workflowIdForMessage, ignoreAlreadyExists } from "./executor.js";
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/a2a/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,WAAW,EACX,uBAAuB,EACvB,kBAAkB,EAGnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,cAAc,EAKf,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,yBAAyB,EACzB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,yBAAyB,EACzB,eAAe,EACf,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,cAAc,EAMf,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,QAAQ,EACR,SAAS,EACT,MAAM,EACN,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EAChB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAElE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,OAAO,EACL,WAAW,EACX,oBAAoB,EACpB,mBAAmB,EAIpB,MAAM,eAAe,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { type JWK } from "jose";
2
+ import { type Task } from "@a2a-js/sdk";
3
+ import type { PlainTask } from "./task.js";
4
+ /**
5
+ * Header carrying the per-task validation `token` the gateway set in the
6
+ * `taskPushNotificationConfig`. Echoed verbatim so the gateway can correlate the
7
+ * callback to its pending task row. Must match looping-gateway's
8
+ * `NOTIFICATION_TOKEN_HEADER` (`src/a2a/notifications/remote.ts`).
9
+ */
10
+ export declare const NOTIFICATION_TOKEN_HEADER = "x-a2a-notification-token";
11
+ /**
12
+ * The `submitted` Task we return synchronously to accept a turn (A2A §7.2). The
13
+ * gateway only requires a non-empty `id`; the actual reply follows later via the
14
+ * callback.
15
+ */
16
+ export declare function buildSubmittedTask(taskId: string, contextId: string): PlainTask;
17
+ /**
18
+ * The terminal `completed` Task for a turn the agent deliberately did not answer
19
+ * (it called the `no_reply` tool — see the agent loop). Same
20
+ * shape as {@link buildSubmittedTask}: **no `status.message` at all**.
21
+ *
22
+ * The callback is still POSTed. The gateway's pending row has to resolve — we
23
+ * simply hand it nothing to post to Slack. There is no `messageId` because there
24
+ * is no message, so unlike {@link buildCompletedTask} nothing needs a stable id
25
+ * for the gateway to dedupe on: a `notify`-step retry re-delivers no content and
26
+ * is idempotent by construction.
27
+ */
28
+ export declare function buildNoReplyCompletedTask(taskId: string, contextId: string): PlainTask;
29
+ /**
30
+ * A non-terminal `working` Task snapshot carrying an intermediate content message.
31
+ * Streamed live from the DO as the tool loop emits content before the final reply.
32
+ * `messageId` is derived from `${taskId}:${stepIndex}` — stable across re-runs (see
33
+ * {@link agentTextMessage}) so the gateway dedupes correctly on workflow replay.
34
+ */
35
+ export declare function buildWorkingTask(taskId: string, contextId: string, text: string, stepIndex: number): PlainTask;
36
+ /**
37
+ * The terminal `completed` Task POSTed to the gateway callback. The `messageId` is
38
+ * deterministic (`${taskId}:final`, not a fresh UUID) because this is built in the
39
+ * workflow body, which re-runs on replay: a random id would change on a notify-step
40
+ * retry and the gateway would dedupe the final message as a new one and double-post.
41
+ */
42
+ export declare function buildCompletedTask(taskId: string, contextId: string, reply: string): PlainTask;
43
+ /**
44
+ * The terminal `failed` Task POSTed to the gateway callback — an unexpected,
45
+ * non-transient failure aborted the turn.
46
+ *
47
+ * A2A v1.0 gives a task no structured error (`TaskStatus` is only
48
+ * `{state, message, timestamp}`), so the state *is* the failure signal and `text`
49
+ * is the only place to explain. Keep that text user-safe: the gateway renders it
50
+ * to a human, under its own "⚠️ *Agent …* (failed):" prefix.
51
+ *
52
+ * Shares the `${taskId}:final` messageId with {@link buildCompletedTask} by
53
+ * design: a Task terminates exactly once and the two states are mutually
54
+ * exclusive, so only one of them is ever built and posted — and a notify retry
55
+ * re-posts that same one under the same dedupe key.
56
+ */
57
+ export declare function buildFailedTask(taskId: string, contextId: string, text: string): PlainTask;
58
+ /**
59
+ * Sign the callback JWT the gateway verifies against our pinned card key. The
60
+ * protected header mirrors the card signature (`kid`+`jku`); `aud` must equal the
61
+ * exact webhook URL the gateway handed us in the `taskPushNotificationConfig`.
62
+ */
63
+ export declare function signCallbackJwt(privateJwk: JWK & {
64
+ kid: string;
65
+ }, opts: {
66
+ jku: string;
67
+ aud: string;
68
+ }): Promise<string>;
69
+ /**
70
+ * POST a Task snapshot to the gateway's push-notification webhook, wrapped in the
71
+ * v1.0 `StreamResponse` envelope. Returns the raw `Response` so the caller (the
72
+ * workflow's `notify` step) can decide whether a non-2xx warrants a retry.
73
+ */
74
+ export declare function postNotification(url: string, token: string, jwt: string, task: Task): Promise<Response>;
75
+ //# sourceMappingURL=notify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/a2a/notify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,GAAG,EAAE,MAAM,MAAM,CAAC;AACzC,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAqB3C;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,6BAA6B,CAAC;AAqCpE;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,SAAS,CAEX;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,SAAS,CAEX;AAwBD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,SAAS,CAQX;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,SAAS,CAQX;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,SAAS,CAQX;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,GAAG,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EACjC,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GACjC,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,QAAQ,CAAC,CAanB"}
@@ -0,0 +1,161 @@
1
+ import { SignJWT } from "jose";
2
+ import { TaskState } from "@a2a-js/sdk";
3
+ import { V1PushNotificationSerializer } from "@a2a-js/sdk/server";
4
+ import { agentTextMessage } from "./parts.js";
5
+ /**
6
+ * Outbound push-notification (accept + notify) helpers — the "notify" half of the
7
+ * async A2A contract. The gateway dispatches `SendMessage` with a
8
+ * `taskPushNotificationConfig` (webhook `url` + validation `token`), we accept
9
+ * immediately with a `submitted` Task, and later POST the terminal Task back to
10
+ * that webhook. This module builds the Task shapes and signs + sends that
11
+ * callback.
12
+ *
13
+ * The callback is authenticated exactly like the AgentCard: a short-lived EdDSA
14
+ * JWT signed by `A2A_SIGNING_KEY`, whose protected header `kid`+`jku` must equal
15
+ * the card's signing `kid`+`jku` (see {@link file://./card.ts} `signCard`) — the
16
+ * gateway pinned those at registration (Trust-On-First-Use) and verifies the
17
+ * callback token against that same public JWKS. No shared secret crosses the
18
+ * boundary; only our public key is ever used to verify.
19
+ */
20
+ /** JWS algorithm — must match the card + gateway (`EdDSA`). */
21
+ const ALG = "EdDSA";
22
+ /**
23
+ * Header carrying the per-task validation `token` the gateway set in the
24
+ * `taskPushNotificationConfig`. Echoed verbatim so the gateway can correlate the
25
+ * callback to its pending task row. Must match looping-gateway's
26
+ * `NOTIFICATION_TOKEN_HEADER` (`src/a2a/notifications/remote.ts`).
27
+ */
28
+ export const NOTIFICATION_TOKEN_HEADER = "x-a2a-notification-token";
29
+ /**
30
+ * Callback-JWT lifetime. The gateway enforces `maxTokenAge: 10m` with a 60s clock
31
+ * tolerance, so keep this comfortably under that.
32
+ */
33
+ const CALLBACK_TOKEN_TTL = "5m";
34
+ /**
35
+ * The SDK's canonical v1.0 push-notification body encoder: the `StreamResponse`
36
+ * envelope as protobuf-JSON, with content type `application/a2a+json`. v1.0
37
+ * moved push notifications onto the same envelope the streaming transports use
38
+ * (v0.3 POSTed a bare `Task`), so the encoding is the SDK's rather than ours —
39
+ * the gateway decodes it with `StreamResponse.fromJSON`.
40
+ */
41
+ const PUSH_SERIALIZER = new V1PushNotificationSerializer();
42
+ /** A Task snapshot in `state` carrying no message — nothing to say, only a state change. */
43
+ function buildBareTask(taskId, contextId, state) {
44
+ return {
45
+ id: taskId,
46
+ contextId,
47
+ status: {
48
+ state,
49
+ message: undefined,
50
+ timestamp: new Date().toISOString()
51
+ },
52
+ artifacts: [],
53
+ history: [],
54
+ metadata: undefined
55
+ };
56
+ }
57
+ /**
58
+ * The `submitted` Task we return synchronously to accept a turn (A2A §7.2). The
59
+ * gateway only requires a non-empty `id`; the actual reply follows later via the
60
+ * callback.
61
+ */
62
+ export function buildSubmittedTask(taskId, contextId) {
63
+ return buildBareTask(taskId, contextId, TaskState.TASK_STATE_SUBMITTED);
64
+ }
65
+ /**
66
+ * The terminal `completed` Task for a turn the agent deliberately did not answer
67
+ * (it called the `no_reply` tool — see the agent loop). Same
68
+ * shape as {@link buildSubmittedTask}: **no `status.message` at all**.
69
+ *
70
+ * The callback is still POSTed. The gateway's pending row has to resolve — we
71
+ * simply hand it nothing to post to Slack. There is no `messageId` because there
72
+ * is no message, so unlike {@link buildCompletedTask} nothing needs a stable id
73
+ * for the gateway to dedupe on: a `notify`-step retry re-delivers no content and
74
+ * is idempotent by construction.
75
+ */
76
+ export function buildNoReplyCompletedTask(taskId, contextId) {
77
+ return buildBareTask(taskId, contextId, TaskState.TASK_STATE_COMPLETED);
78
+ }
79
+ /**
80
+ * A Task snapshot POSTed to the gateway callback in a given `state`, carrying one
81
+ * `agent` message. The gateway reads the reply from `status.message.parts`, so
82
+ * the text lives there.
83
+ */
84
+ function buildTaskUpdate(taskId, contextId, state, text, messageId) {
85
+ const task = buildBareTask(taskId, contextId, state);
86
+ task.status.message = agentTextMessage({
87
+ messageId,
88
+ text,
89
+ contextId,
90
+ taskId
91
+ });
92
+ return task;
93
+ }
94
+ /**
95
+ * A non-terminal `working` Task snapshot carrying an intermediate content message.
96
+ * Streamed live from the DO as the tool loop emits content before the final reply.
97
+ * `messageId` is derived from `${taskId}:${stepIndex}` — stable across re-runs (see
98
+ * {@link agentTextMessage}) so the gateway dedupes correctly on workflow replay.
99
+ */
100
+ export function buildWorkingTask(taskId, contextId, text, stepIndex) {
101
+ return buildTaskUpdate(taskId, contextId, TaskState.TASK_STATE_WORKING, text, `${taskId}:${stepIndex}`);
102
+ }
103
+ /**
104
+ * The terminal `completed` Task POSTed to the gateway callback. The `messageId` is
105
+ * deterministic (`${taskId}:final`, not a fresh UUID) because this is built in the
106
+ * workflow body, which re-runs on replay: a random id would change on a notify-step
107
+ * retry and the gateway would dedupe the final message as a new one and double-post.
108
+ */
109
+ export function buildCompletedTask(taskId, contextId, reply) {
110
+ return buildTaskUpdate(taskId, contextId, TaskState.TASK_STATE_COMPLETED, reply, `${taskId}:final`);
111
+ }
112
+ /**
113
+ * The terminal `failed` Task POSTed to the gateway callback — an unexpected,
114
+ * non-transient failure aborted the turn.
115
+ *
116
+ * A2A v1.0 gives a task no structured error (`TaskStatus` is only
117
+ * `{state, message, timestamp}`), so the state *is* the failure signal and `text`
118
+ * is the only place to explain. Keep that text user-safe: the gateway renders it
119
+ * to a human, under its own "⚠️ *Agent …* (failed):" prefix.
120
+ *
121
+ * Shares the `${taskId}:final` messageId with {@link buildCompletedTask} by
122
+ * design: a Task terminates exactly once and the two states are mutually
123
+ * exclusive, so only one of them is ever built and posted — and a notify retry
124
+ * re-posts that same one under the same dedupe key.
125
+ */
126
+ export function buildFailedTask(taskId, contextId, text) {
127
+ return buildTaskUpdate(taskId, contextId, TaskState.TASK_STATE_FAILED, text, `${taskId}:final`);
128
+ }
129
+ /**
130
+ * Sign the callback JWT the gateway verifies against our pinned card key. The
131
+ * protected header mirrors the card signature (`kid`+`jku`); `aud` must equal the
132
+ * exact webhook URL the gateway handed us in the `taskPushNotificationConfig`.
133
+ */
134
+ export async function signCallbackJwt(privateJwk, opts) {
135
+ return new SignJWT({})
136
+ .setProtectedHeader({ alg: ALG, kid: privateJwk.kid, jku: opts.jku })
137
+ .setAudience(opts.aud)
138
+ .setIssuedAt()
139
+ .setExpirationTime(CALLBACK_TOKEN_TTL)
140
+ .sign(privateJwk);
141
+ }
142
+ /**
143
+ * POST a Task snapshot to the gateway's push-notification webhook, wrapped in the
144
+ * v1.0 `StreamResponse` envelope. Returns the raw `Response` so the caller (the
145
+ * workflow's `notify` step) can decide whether a non-2xx warrants a retry.
146
+ */
147
+ export async function postNotification(url, token, jwt, task) {
148
+ const { body, contentType } = PUSH_SERIALIZER.serialize({
149
+ payload: { $case: "task", value: task }
150
+ });
151
+ return fetch(url, {
152
+ method: "POST",
153
+ headers: {
154
+ "content-type": contentType,
155
+ authorization: `Bearer ${jwt}`,
156
+ [NOTIFICATION_TOKEN_HEADER]: token
157
+ },
158
+ body
159
+ });
160
+ }
161
+ //# sourceMappingURL=notify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/a2a/notify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { type Message, type Part } from "@a2a-js/sdk";
2
+ import type { PlainMessage, PlainPart } from "./task.js";
3
+ /**
4
+ * A `text` part carrying `text`. Typed as the narrowed {@link PlainPart} (which
5
+ * widens to `Part` for free) so a message built here can cross the DO RPC
6
+ * boundary — see {@link file://./task.ts}.
7
+ */
8
+ export declare function textPart(text: string): PlainPart;
9
+ /** Concatenate the text of every `text` part, trimming surrounding whitespace. */
10
+ export declare function partsText(parts: Part[] | undefined): string;
11
+ /** The plain-text content of an inbound A2A message (what the caller said). */
12
+ export declare function textOf(message: Message): string;
13
+ /**
14
+ * Bounds the inbound user text carried in a durable Workflow payload (UTF-8).
15
+ * Workflow params have a platform size limit, and a caller's message is the one
16
+ * unbounded thing that goes into them.
17
+ */
18
+ export declare const MAX_INBOUND_TEXT_BYTES: number;
19
+ /** Invalid inbound content that must not cross the A2A-to-workflow boundary. */
20
+ export declare class InboundPartError extends Error {
21
+ constructor(message: string);
22
+ }
23
+ /**
24
+ * Extract and validate the user-turn text. Rejects a message with no usable
25
+ * text, and enforces a single UTF-8 size bound *before* the text enters a
26
+ * workflow payload — past that point the failure is a workflow that will not
27
+ * start, with no request left to answer on.
28
+ *
29
+ * File and data parts are deliberately out of scope: only text crosses into the
30
+ * agent runtime.
31
+ */
32
+ export declare function inboundText(message: Message): string;
33
+ /**
34
+ * An `agent`-role message carrying one text part, with the proto-required
35
+ * fields this agent never sets filled in. `messageId` is the gateway's dedupe
36
+ * key, so callers pass a **stable** id (never a fresh random per attempt) — a
37
+ * callback the workflow/DO re-runs must reuse the same id or the gateway
38
+ * treats the replay as a new message and double-posts.
39
+ */
40
+ export declare function agentTextMessage(input: {
41
+ messageId: string;
42
+ text: string;
43
+ contextId: string;
44
+ taskId: string;
45
+ }): PlainMessage;
46
+ //# sourceMappingURL=parts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../../src/a2a/parts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAmBzD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAOhD;AAED,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,SAAS,GAAG,MAAM,CAM3D;AAED,+EAA+E;AAC/E,wBAAgB,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAE/C;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,QAAa,CAAC;AAIjD,gFAAgF;AAChF,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CASpD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,YAAY,CAWf"}
@@ -0,0 +1,95 @@
1
+ import { Role } from "@a2a-js/sdk";
2
+ /**
3
+ * Constructors and readers for A2A v1.0 message content — the one place the
4
+ * adapter reaches into the `@a2a-js/sdk` message shape. Everything past
5
+ * {@link file://./executor.ts A2AExecutor} works in plain strings, so the agent
6
+ * runtime never sees an A2A type.
7
+ *
8
+ * The v1.0 data model is generated from the protobuf schema, so the wire types
9
+ * are "all fields present": a `Part` carries a `content` oneof discriminated by
10
+ * `$case` (plus `filename`/`mediaType`) instead of v0.3's `kind`, and a
11
+ * `Message` carries empty strings and empty arrays rather than omitted
12
+ * optionals. Hand-writing those literals at every call site is noisy and easy to
13
+ * get subtly wrong, so every part/message this agent emits is built here.
14
+ */
15
+ /** Media type stamped on the text parts this agent emits. */
16
+ const TEXT_MEDIA_TYPE = "text/plain";
17
+ /**
18
+ * A `text` part carrying `text`. Typed as the narrowed {@link PlainPart} (which
19
+ * widens to `Part` for free) so a message built here can cross the DO RPC
20
+ * boundary — see {@link file://./task.ts}.
21
+ */
22
+ export function textPart(text) {
23
+ return {
24
+ content: { $case: "text", value: text },
25
+ metadata: undefined,
26
+ filename: "",
27
+ mediaType: TEXT_MEDIA_TYPE
28
+ };
29
+ }
30
+ /** Concatenate the text of every `text` part, trimming surrounding whitespace. */
31
+ export function partsText(parts) {
32
+ let out = "";
33
+ for (const part of parts ?? []) {
34
+ if (part.content?.$case === "text")
35
+ out += part.content.value;
36
+ }
37
+ return out.trim();
38
+ }
39
+ /** The plain-text content of an inbound A2A message (what the caller said). */
40
+ export function textOf(message) {
41
+ return partsText(message.parts);
42
+ }
43
+ /**
44
+ * Bounds the inbound user text carried in a durable Workflow payload (UTF-8).
45
+ * Workflow params have a platform size limit, and a caller's message is the one
46
+ * unbounded thing that goes into them.
47
+ */
48
+ export const MAX_INBOUND_TEXT_BYTES = 256 * 1024;
49
+ const encoder = new TextEncoder();
50
+ /** Invalid inbound content that must not cross the A2A-to-workflow boundary. */
51
+ export class InboundPartError extends Error {
52
+ constructor(message) {
53
+ super(message);
54
+ this.name = "InboundPartError";
55
+ }
56
+ }
57
+ /**
58
+ * Extract and validate the user-turn text. Rejects a message with no usable
59
+ * text, and enforces a single UTF-8 size bound *before* the text enters a
60
+ * workflow payload — past that point the failure is a workflow that will not
61
+ * start, with no request left to answer on.
62
+ *
63
+ * File and data parts are deliberately out of scope: only text crosses into the
64
+ * agent runtime.
65
+ */
66
+ export function inboundText(message) {
67
+ const text = textOf(message);
68
+ if (!text) {
69
+ throw new InboundPartError("message has no usable text");
70
+ }
71
+ if (encoder.encode(text).byteLength > MAX_INBOUND_TEXT_BYTES) {
72
+ throw new InboundPartError("message text exceeds the size limit");
73
+ }
74
+ return text;
75
+ }
76
+ /**
77
+ * An `agent`-role message carrying one text part, with the proto-required
78
+ * fields this agent never sets filled in. `messageId` is the gateway's dedupe
79
+ * key, so callers pass a **stable** id (never a fresh random per attempt) — a
80
+ * callback the workflow/DO re-runs must reuse the same id or the gateway
81
+ * treats the replay as a new message and double-posts.
82
+ */
83
+ export function agentTextMessage(input) {
84
+ return {
85
+ messageId: input.messageId,
86
+ role: Role.ROLE_AGENT,
87
+ parts: [textPart(input.text)],
88
+ contextId: input.contextId,
89
+ taskId: input.taskId,
90
+ metadata: undefined,
91
+ extensions: [],
92
+ referenceTaskIds: []
93
+ };
94
+ }
95
+ //# sourceMappingURL=parts.js.map