@renseiai/agentfactory 0.8.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 (246) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -0
  3. package/dist/src/config/index.d.ts +3 -0
  4. package/dist/src/config/index.d.ts.map +1 -0
  5. package/dist/src/config/index.js +1 -0
  6. package/dist/src/config/repository-config.d.ts +44 -0
  7. package/dist/src/config/repository-config.d.ts.map +1 -0
  8. package/dist/src/config/repository-config.js +88 -0
  9. package/dist/src/config/repository-config.test.d.ts +2 -0
  10. package/dist/src/config/repository-config.test.d.ts.map +1 -0
  11. package/dist/src/config/repository-config.test.js +249 -0
  12. package/dist/src/deployment/deployment-checker.d.ts +110 -0
  13. package/dist/src/deployment/deployment-checker.d.ts.map +1 -0
  14. package/dist/src/deployment/deployment-checker.js +242 -0
  15. package/dist/src/deployment/index.d.ts +3 -0
  16. package/dist/src/deployment/index.d.ts.map +1 -0
  17. package/dist/src/deployment/index.js +2 -0
  18. package/dist/src/frontend/index.d.ts +2 -0
  19. package/dist/src/frontend/index.d.ts.map +1 -0
  20. package/dist/src/frontend/index.js +1 -0
  21. package/dist/src/frontend/types.d.ts +106 -0
  22. package/dist/src/frontend/types.d.ts.map +1 -0
  23. package/dist/src/frontend/types.js +11 -0
  24. package/dist/src/governor/decision-engine.d.ts +52 -0
  25. package/dist/src/governor/decision-engine.d.ts.map +1 -0
  26. package/dist/src/governor/decision-engine.js +220 -0
  27. package/dist/src/governor/decision-engine.test.d.ts +2 -0
  28. package/dist/src/governor/decision-engine.test.d.ts.map +1 -0
  29. package/dist/src/governor/decision-engine.test.js +629 -0
  30. package/dist/src/governor/event-bus.d.ts +43 -0
  31. package/dist/src/governor/event-bus.d.ts.map +1 -0
  32. package/dist/src/governor/event-bus.js +8 -0
  33. package/dist/src/governor/event-deduplicator.d.ts +43 -0
  34. package/dist/src/governor/event-deduplicator.d.ts.map +1 -0
  35. package/dist/src/governor/event-deduplicator.js +53 -0
  36. package/dist/src/governor/event-driven-governor.d.ts +131 -0
  37. package/dist/src/governor/event-driven-governor.d.ts.map +1 -0
  38. package/dist/src/governor/event-driven-governor.js +379 -0
  39. package/dist/src/governor/event-driven-governor.test.d.ts +2 -0
  40. package/dist/src/governor/event-driven-governor.test.d.ts.map +1 -0
  41. package/dist/src/governor/event-driven-governor.test.js +673 -0
  42. package/dist/src/governor/event-types.d.ts +78 -0
  43. package/dist/src/governor/event-types.d.ts.map +1 -0
  44. package/dist/src/governor/event-types.js +32 -0
  45. package/dist/src/governor/governor-types.d.ts +82 -0
  46. package/dist/src/governor/governor-types.d.ts.map +1 -0
  47. package/dist/src/governor/governor-types.js +21 -0
  48. package/dist/src/governor/governor.d.ts +100 -0
  49. package/dist/src/governor/governor.d.ts.map +1 -0
  50. package/dist/src/governor/governor.js +262 -0
  51. package/dist/src/governor/governor.test.d.ts +2 -0
  52. package/dist/src/governor/governor.test.d.ts.map +1 -0
  53. package/dist/src/governor/governor.test.js +514 -0
  54. package/dist/src/governor/human-touchpoints.d.ts +131 -0
  55. package/dist/src/governor/human-touchpoints.d.ts.map +1 -0
  56. package/dist/src/governor/human-touchpoints.js +251 -0
  57. package/dist/src/governor/human-touchpoints.test.d.ts +2 -0
  58. package/dist/src/governor/human-touchpoints.test.d.ts.map +1 -0
  59. package/dist/src/governor/human-touchpoints.test.js +366 -0
  60. package/dist/src/governor/in-memory-event-bus.d.ts +29 -0
  61. package/dist/src/governor/in-memory-event-bus.d.ts.map +1 -0
  62. package/dist/src/governor/in-memory-event-bus.js +79 -0
  63. package/dist/src/governor/index.d.ts +14 -0
  64. package/dist/src/governor/index.d.ts.map +1 -0
  65. package/dist/src/governor/index.js +13 -0
  66. package/dist/src/governor/override-parser.d.ts +60 -0
  67. package/dist/src/governor/override-parser.d.ts.map +1 -0
  68. package/dist/src/governor/override-parser.js +98 -0
  69. package/dist/src/governor/override-parser.test.d.ts +2 -0
  70. package/dist/src/governor/override-parser.test.d.ts.map +1 -0
  71. package/dist/src/governor/override-parser.test.js +312 -0
  72. package/dist/src/governor/platform-adapter.d.ts +69 -0
  73. package/dist/src/governor/platform-adapter.d.ts.map +1 -0
  74. package/dist/src/governor/platform-adapter.js +11 -0
  75. package/dist/src/governor/processing-state.d.ts +66 -0
  76. package/dist/src/governor/processing-state.d.ts.map +1 -0
  77. package/dist/src/governor/processing-state.js +43 -0
  78. package/dist/src/governor/processing-state.test.d.ts +2 -0
  79. package/dist/src/governor/processing-state.test.d.ts.map +1 -0
  80. package/dist/src/governor/processing-state.test.js +96 -0
  81. package/dist/src/governor/top-of-funnel.d.ts +118 -0
  82. package/dist/src/governor/top-of-funnel.d.ts.map +1 -0
  83. package/dist/src/governor/top-of-funnel.js +168 -0
  84. package/dist/src/governor/top-of-funnel.test.d.ts +2 -0
  85. package/dist/src/governor/top-of-funnel.test.d.ts.map +1 -0
  86. package/dist/src/governor/top-of-funnel.test.js +331 -0
  87. package/dist/src/index.d.ts +11 -0
  88. package/dist/src/index.d.ts.map +1 -0
  89. package/dist/src/index.js +10 -0
  90. package/dist/src/linear-cli.d.ts +38 -0
  91. package/dist/src/linear-cli.d.ts.map +1 -0
  92. package/dist/src/linear-cli.js +674 -0
  93. package/dist/src/logger.d.ts +117 -0
  94. package/dist/src/logger.d.ts.map +1 -0
  95. package/dist/src/logger.js +430 -0
  96. package/dist/src/manifest/generate.d.ts +20 -0
  97. package/dist/src/manifest/generate.d.ts.map +1 -0
  98. package/dist/src/manifest/generate.js +65 -0
  99. package/dist/src/manifest/index.d.ts +4 -0
  100. package/dist/src/manifest/index.d.ts.map +1 -0
  101. package/dist/src/manifest/index.js +2 -0
  102. package/dist/src/manifest/route-manifest.d.ts +34 -0
  103. package/dist/src/manifest/route-manifest.d.ts.map +1 -0
  104. package/dist/src/manifest/route-manifest.js +148 -0
  105. package/dist/src/orchestrator/activity-emitter.d.ts +119 -0
  106. package/dist/src/orchestrator/activity-emitter.d.ts.map +1 -0
  107. package/dist/src/orchestrator/activity-emitter.js +306 -0
  108. package/dist/src/orchestrator/api-activity-emitter.d.ts +167 -0
  109. package/dist/src/orchestrator/api-activity-emitter.d.ts.map +1 -0
  110. package/dist/src/orchestrator/api-activity-emitter.js +417 -0
  111. package/dist/src/orchestrator/heartbeat-writer.d.ts +57 -0
  112. package/dist/src/orchestrator/heartbeat-writer.d.ts.map +1 -0
  113. package/dist/src/orchestrator/heartbeat-writer.js +137 -0
  114. package/dist/src/orchestrator/index.d.ts +20 -0
  115. package/dist/src/orchestrator/index.d.ts.map +1 -0
  116. package/dist/src/orchestrator/index.js +22 -0
  117. package/dist/src/orchestrator/log-analyzer.d.ts +160 -0
  118. package/dist/src/orchestrator/log-analyzer.d.ts.map +1 -0
  119. package/dist/src/orchestrator/log-analyzer.js +572 -0
  120. package/dist/src/orchestrator/log-config.d.ts +39 -0
  121. package/dist/src/orchestrator/log-config.d.ts.map +1 -0
  122. package/dist/src/orchestrator/log-config.js +45 -0
  123. package/dist/src/orchestrator/orchestrator.d.ts +316 -0
  124. package/dist/src/orchestrator/orchestrator.d.ts.map +1 -0
  125. package/dist/src/orchestrator/orchestrator.js +3290 -0
  126. package/dist/src/orchestrator/parse-work-result.d.ts +16 -0
  127. package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -0
  128. package/dist/src/orchestrator/parse-work-result.js +135 -0
  129. package/dist/src/orchestrator/parse-work-result.test.d.ts +2 -0
  130. package/dist/src/orchestrator/parse-work-result.test.d.ts.map +1 -0
  131. package/dist/src/orchestrator/parse-work-result.test.js +234 -0
  132. package/dist/src/orchestrator/progress-logger.d.ts +72 -0
  133. package/dist/src/orchestrator/progress-logger.d.ts.map +1 -0
  134. package/dist/src/orchestrator/progress-logger.js +135 -0
  135. package/dist/src/orchestrator/session-logger.d.ts +159 -0
  136. package/dist/src/orchestrator/session-logger.d.ts.map +1 -0
  137. package/dist/src/orchestrator/session-logger.js +275 -0
  138. package/dist/src/orchestrator/state-recovery.d.ts +96 -0
  139. package/dist/src/orchestrator/state-recovery.d.ts.map +1 -0
  140. package/dist/src/orchestrator/state-recovery.js +302 -0
  141. package/dist/src/orchestrator/state-types.d.ts +165 -0
  142. package/dist/src/orchestrator/state-types.d.ts.map +1 -0
  143. package/dist/src/orchestrator/state-types.js +7 -0
  144. package/dist/src/orchestrator/stream-parser.d.ts +151 -0
  145. package/dist/src/orchestrator/stream-parser.d.ts.map +1 -0
  146. package/dist/src/orchestrator/stream-parser.js +137 -0
  147. package/dist/src/orchestrator/types.d.ts +232 -0
  148. package/dist/src/orchestrator/types.d.ts.map +1 -0
  149. package/dist/src/orchestrator/types.js +4 -0
  150. package/dist/src/orchestrator/validate-git-remote.test.d.ts +2 -0
  151. package/dist/src/orchestrator/validate-git-remote.test.d.ts.map +1 -0
  152. package/dist/src/orchestrator/validate-git-remote.test.js +61 -0
  153. package/dist/src/providers/a2a-auth.d.ts +81 -0
  154. package/dist/src/providers/a2a-auth.d.ts.map +1 -0
  155. package/dist/src/providers/a2a-auth.js +188 -0
  156. package/dist/src/providers/a2a-auth.test.d.ts +2 -0
  157. package/dist/src/providers/a2a-auth.test.d.ts.map +1 -0
  158. package/dist/src/providers/a2a-auth.test.js +232 -0
  159. package/dist/src/providers/a2a-provider.d.ts +254 -0
  160. package/dist/src/providers/a2a-provider.d.ts.map +1 -0
  161. package/dist/src/providers/a2a-provider.integration.test.d.ts +9 -0
  162. package/dist/src/providers/a2a-provider.integration.test.d.ts.map +1 -0
  163. package/dist/src/providers/a2a-provider.integration.test.js +665 -0
  164. package/dist/src/providers/a2a-provider.js +811 -0
  165. package/dist/src/providers/a2a-provider.test.d.ts +2 -0
  166. package/dist/src/providers/a2a-provider.test.d.ts.map +1 -0
  167. package/dist/src/providers/a2a-provider.test.js +681 -0
  168. package/dist/src/providers/amp-provider.d.ts +20 -0
  169. package/dist/src/providers/amp-provider.d.ts.map +1 -0
  170. package/dist/src/providers/amp-provider.js +24 -0
  171. package/dist/src/providers/claude-provider.d.ts +18 -0
  172. package/dist/src/providers/claude-provider.d.ts.map +1 -0
  173. package/dist/src/providers/claude-provider.js +437 -0
  174. package/dist/src/providers/codex-provider.d.ts +133 -0
  175. package/dist/src/providers/codex-provider.d.ts.map +1 -0
  176. package/dist/src/providers/codex-provider.js +381 -0
  177. package/dist/src/providers/codex-provider.test.d.ts +2 -0
  178. package/dist/src/providers/codex-provider.test.d.ts.map +1 -0
  179. package/dist/src/providers/codex-provider.test.js +387 -0
  180. package/dist/src/providers/index.d.ts +44 -0
  181. package/dist/src/providers/index.d.ts.map +1 -0
  182. package/dist/src/providers/index.js +85 -0
  183. package/dist/src/providers/spring-ai-provider.d.ts +90 -0
  184. package/dist/src/providers/spring-ai-provider.d.ts.map +1 -0
  185. package/dist/src/providers/spring-ai-provider.integration.test.d.ts +13 -0
  186. package/dist/src/providers/spring-ai-provider.integration.test.d.ts.map +1 -0
  187. package/dist/src/providers/spring-ai-provider.integration.test.js +351 -0
  188. package/dist/src/providers/spring-ai-provider.js +317 -0
  189. package/dist/src/providers/spring-ai-provider.test.d.ts +2 -0
  190. package/dist/src/providers/spring-ai-provider.test.d.ts.map +1 -0
  191. package/dist/src/providers/spring-ai-provider.test.js +200 -0
  192. package/dist/src/providers/types.d.ts +165 -0
  193. package/dist/src/providers/types.d.ts.map +1 -0
  194. package/dist/src/providers/types.js +13 -0
  195. package/dist/src/templates/adapters.d.ts +51 -0
  196. package/dist/src/templates/adapters.d.ts.map +1 -0
  197. package/dist/src/templates/adapters.js +104 -0
  198. package/dist/src/templates/adapters.test.d.ts +2 -0
  199. package/dist/src/templates/adapters.test.d.ts.map +1 -0
  200. package/dist/src/templates/adapters.test.js +165 -0
  201. package/dist/src/templates/agent-definition.d.ts +85 -0
  202. package/dist/src/templates/agent-definition.d.ts.map +1 -0
  203. package/dist/src/templates/agent-definition.js +97 -0
  204. package/dist/src/templates/agent-definition.test.d.ts +2 -0
  205. package/dist/src/templates/agent-definition.test.d.ts.map +1 -0
  206. package/dist/src/templates/agent-definition.test.js +209 -0
  207. package/dist/src/templates/index.d.ts +14 -0
  208. package/dist/src/templates/index.d.ts.map +1 -0
  209. package/dist/src/templates/index.js +11 -0
  210. package/dist/src/templates/loader.d.ts +41 -0
  211. package/dist/src/templates/loader.d.ts.map +1 -0
  212. package/dist/src/templates/loader.js +114 -0
  213. package/dist/src/templates/registry.d.ts +80 -0
  214. package/dist/src/templates/registry.d.ts.map +1 -0
  215. package/dist/src/templates/registry.js +177 -0
  216. package/dist/src/templates/registry.test.d.ts +2 -0
  217. package/dist/src/templates/registry.test.d.ts.map +1 -0
  218. package/dist/src/templates/registry.test.js +198 -0
  219. package/dist/src/templates/renderer.d.ts +29 -0
  220. package/dist/src/templates/renderer.d.ts.map +1 -0
  221. package/dist/src/templates/renderer.js +35 -0
  222. package/dist/src/templates/strategy-templates.test.d.ts +2 -0
  223. package/dist/src/templates/strategy-templates.test.d.ts.map +1 -0
  224. package/dist/src/templates/strategy-templates.test.js +619 -0
  225. package/dist/src/templates/types.d.ts +233 -0
  226. package/dist/src/templates/types.d.ts.map +1 -0
  227. package/dist/src/templates/types.js +127 -0
  228. package/dist/src/templates/types.test.d.ts +2 -0
  229. package/dist/src/templates/types.test.d.ts.map +1 -0
  230. package/dist/src/templates/types.test.js +232 -0
  231. package/dist/src/tools/index.d.ts +6 -0
  232. package/dist/src/tools/index.d.ts.map +1 -0
  233. package/dist/src/tools/index.js +3 -0
  234. package/dist/src/tools/linear-runner.d.ts +34 -0
  235. package/dist/src/tools/linear-runner.d.ts.map +1 -0
  236. package/dist/src/tools/linear-runner.js +700 -0
  237. package/dist/src/tools/plugins/linear.d.ts +9 -0
  238. package/dist/src/tools/plugins/linear.d.ts.map +1 -0
  239. package/dist/src/tools/plugins/linear.js +138 -0
  240. package/dist/src/tools/registry.d.ts +9 -0
  241. package/dist/src/tools/registry.d.ts.map +1 -0
  242. package/dist/src/tools/registry.js +18 -0
  243. package/dist/src/tools/types.d.ts +18 -0
  244. package/dist/src/tools/types.d.ts.map +1 -0
  245. package/dist/src/tools/types.js +1 -0
  246. package/package.json +78 -0
@@ -0,0 +1,188 @@
1
+ /**
2
+ * A2A Authentication Utilities
3
+ *
4
+ * Client-side: resolve credentials from env vars and build HTTP auth headers
5
+ * for outbound requests to remote A2A agents.
6
+ *
7
+ * Server-side: validate inbound A2A request auth using timing-safe comparison,
8
+ * following the same pattern as worker-auth.ts.
9
+ */
10
+ import crypto from 'crypto';
11
+ // ---------------------------------------------------------------------------
12
+ // Client-side: credential resolution
13
+ // ---------------------------------------------------------------------------
14
+ /**
15
+ * Normalise a hostname into an env-var suffix.
16
+ *
17
+ * - Uppercases
18
+ * - Replaces dots and hyphens with underscores
19
+ *
20
+ * Example: "spring-agent.example.com" -> "SPRING_AGENT_EXAMPLE_COM"
21
+ */
22
+ function hostnameToEnvSuffix(hostname) {
23
+ return hostname.toUpperCase().replace(/[.\-]/g, '_');
24
+ }
25
+ /**
26
+ * Resolve credentials from environment variables for outbound A2A requests.
27
+ *
28
+ * Resolution order:
29
+ * 1. If agentUrl is provided, try host-specific env vars first:
30
+ * - A2A_API_KEY_{HOSTNAME}
31
+ * - A2A_BEARER_TOKEN_{HOSTNAME}
32
+ * 2. Fall back to generic env vars:
33
+ * - A2A_API_KEY
34
+ * - A2A_BEARER_TOKEN
35
+ *
36
+ * @param env - Environment variable map (e.g. process.env)
37
+ * @param agentUrl - Optional URL of the remote agent for host-specific lookup
38
+ * @returns Resolved credentials (may be empty if no env vars are set)
39
+ */
40
+ export function resolveA2aCredentials(env, agentUrl) {
41
+ const creds = {};
42
+ let hostSuffix;
43
+ if (agentUrl) {
44
+ try {
45
+ const url = new URL(agentUrl);
46
+ hostSuffix = hostnameToEnvSuffix(url.hostname);
47
+ }
48
+ catch {
49
+ // Invalid URL — skip host-specific lookup
50
+ }
51
+ }
52
+ // Resolve API key: host-specific first, then generic
53
+ if (hostSuffix) {
54
+ const hostKey = env[`A2A_API_KEY_${hostSuffix}`];
55
+ if (hostKey) {
56
+ creds.apiKey = hostKey;
57
+ }
58
+ }
59
+ if (!creds.apiKey) {
60
+ const genericKey = env['A2A_API_KEY'];
61
+ if (genericKey) {
62
+ creds.apiKey = genericKey;
63
+ }
64
+ }
65
+ // Resolve bearer token: host-specific first, then generic
66
+ if (hostSuffix) {
67
+ const hostToken = env[`A2A_BEARER_TOKEN_${hostSuffix}`];
68
+ if (hostToken) {
69
+ creds.bearerToken = hostToken;
70
+ }
71
+ }
72
+ if (!creds.bearerToken) {
73
+ const genericToken = env['A2A_BEARER_TOKEN'];
74
+ if (genericToken) {
75
+ creds.bearerToken = genericToken;
76
+ }
77
+ }
78
+ return creds;
79
+ }
80
+ // ---------------------------------------------------------------------------
81
+ // Client-side: auth header construction
82
+ // ---------------------------------------------------------------------------
83
+ /**
84
+ * Build HTTP headers for authenticating an outbound A2A request.
85
+ *
86
+ * When auth schemes are provided (from the remote agent's AgentCard), the
87
+ * function matches credentials to the first compatible scheme:
88
+ * - apiKey scheme -> sets header {scheme.name}: {key} (default: x-api-key)
89
+ * - http + bearer -> sets Authorization: Bearer {token}
90
+ *
91
+ * When no auth schemes are provided, auto-detection is used:
92
+ * - bearerToken -> Authorization: Bearer {token}
93
+ * - apiKey -> x-api-key: {key}
94
+ *
95
+ * @param credentials - Resolved credentials
96
+ * @param authSchemes - Optional auth schemes from the remote agent's card
97
+ * @returns Headers object (may be empty)
98
+ */
99
+ export function buildA2aAuthHeaders(credentials, authSchemes) {
100
+ const headers = {};
101
+ if (authSchemes && authSchemes.length > 0) {
102
+ // Match credentials to declared auth schemes
103
+ for (const scheme of authSchemes) {
104
+ if (scheme.type === 'apiKey' && credentials.apiKey) {
105
+ const headerName = scheme.name || 'x-api-key';
106
+ headers[headerName] = credentials.apiKey;
107
+ return headers;
108
+ }
109
+ if (scheme.type === 'http' && scheme.scheme === 'bearer' && credentials.bearerToken) {
110
+ headers['Authorization'] = `Bearer ${credentials.bearerToken}`;
111
+ return headers;
112
+ }
113
+ }
114
+ // No scheme matched — return empty
115
+ return headers;
116
+ }
117
+ // Auto-detect: no auth schemes provided
118
+ if (credentials.bearerToken) {
119
+ headers['Authorization'] = `Bearer ${credentials.bearerToken}`;
120
+ }
121
+ else if (credentials.apiKey) {
122
+ headers['x-api-key'] = credentials.apiKey;
123
+ }
124
+ return headers;
125
+ }
126
+ // ---------------------------------------------------------------------------
127
+ // Server-side: inbound request validation
128
+ // ---------------------------------------------------------------------------
129
+ /**
130
+ * Extract a bearer token or raw key from an Authorization header.
131
+ *
132
+ * Supports:
133
+ * - "Bearer <token>" -> returns the token
134
+ * - Raw value (no prefix) -> returns the value as-is
135
+ */
136
+ function extractToken(authHeader) {
137
+ if (authHeader.startsWith('Bearer ')) {
138
+ return authHeader.slice(7);
139
+ }
140
+ return authHeader;
141
+ }
142
+ /**
143
+ * Validate an incoming A2A request's auth header using timing-safe comparison.
144
+ *
145
+ * Resolution of expected key:
146
+ * 1. config.apiKey (explicit value)
147
+ * 2. env var named by config.apiKeyEnvVar
148
+ * 3. A2A_SERVER_API_KEY env var
149
+ * 4. WORKER_API_KEY env var (fallback for backward compat)
150
+ *
151
+ * If no expected key is configured, returns false (fail closed).
152
+ *
153
+ * @param authHeader - The Authorization header value from the request
154
+ * @param config - Optional auth configuration
155
+ * @returns true if the auth is valid
156
+ */
157
+ export function validateA2aAuth(authHeader, config) {
158
+ if (!authHeader) {
159
+ return false;
160
+ }
161
+ const providedKey = extractToken(authHeader);
162
+ if (!providedKey) {
163
+ return false;
164
+ }
165
+ // Resolve expected key
166
+ let expectedKey;
167
+ if (config?.apiKey) {
168
+ expectedKey = config.apiKey;
169
+ }
170
+ else if (config?.apiKeyEnvVar) {
171
+ expectedKey = process.env[config.apiKeyEnvVar];
172
+ }
173
+ else {
174
+ // Default env var resolution chain
175
+ expectedKey = process.env.A2A_SERVER_API_KEY ?? process.env.WORKER_API_KEY;
176
+ }
177
+ if (!expectedKey) {
178
+ return false;
179
+ }
180
+ // Use timing-safe comparison to prevent timing attacks
181
+ try {
182
+ return crypto.timingSafeEqual(Buffer.from(providedKey), Buffer.from(expectedKey));
183
+ }
184
+ catch {
185
+ // Buffers have different lengths
186
+ return false;
187
+ }
188
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=a2a-auth.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a2a-auth.test.d.ts","sourceRoot":"","sources":["../../../src/providers/a2a-auth.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,232 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { resolveA2aCredentials, buildA2aAuthHeaders, validateA2aAuth, } from './a2a-auth.js';
3
+ // ---------------------------------------------------------------------------
4
+ // resolveA2aCredentials
5
+ // ---------------------------------------------------------------------------
6
+ describe('resolveA2aCredentials', () => {
7
+ it('returns empty credentials when no env vars set', () => {
8
+ const result = resolveA2aCredentials({});
9
+ expect(result).toEqual({});
10
+ });
11
+ it('reads generic A2A_API_KEY', () => {
12
+ const result = resolveA2aCredentials({ A2A_API_KEY: 'my-api-key' });
13
+ expect(result.apiKey).toBe('my-api-key');
14
+ });
15
+ it('reads generic A2A_BEARER_TOKEN', () => {
16
+ const result = resolveA2aCredentials({ A2A_BEARER_TOKEN: 'my-token' });
17
+ expect(result.bearerToken).toBe('my-token');
18
+ });
19
+ it('reads both generic keys simultaneously', () => {
20
+ const result = resolveA2aCredentials({
21
+ A2A_API_KEY: 'key-1',
22
+ A2A_BEARER_TOKEN: 'token-1',
23
+ });
24
+ expect(result.apiKey).toBe('key-1');
25
+ expect(result.bearerToken).toBe('token-1');
26
+ });
27
+ it('reads host-specific keys when agentUrl provided', () => {
28
+ const result = resolveA2aCredentials({ A2A_API_KEY_EXAMPLE_COM: 'host-key' }, 'https://example.com/agent');
29
+ expect(result.apiKey).toBe('host-key');
30
+ });
31
+ it('host-specific takes precedence over generic', () => {
32
+ const result = resolveA2aCredentials({
33
+ A2A_API_KEY: 'generic-key',
34
+ A2A_API_KEY_EXAMPLE_COM: 'host-key',
35
+ A2A_BEARER_TOKEN: 'generic-token',
36
+ A2A_BEARER_TOKEN_EXAMPLE_COM: 'host-token',
37
+ }, 'https://example.com/agent');
38
+ expect(result.apiKey).toBe('host-key');
39
+ expect(result.bearerToken).toBe('host-token');
40
+ });
41
+ it('falls back to generic when host-specific not set', () => {
42
+ const result = resolveA2aCredentials({
43
+ A2A_API_KEY: 'generic-key',
44
+ A2A_BEARER_TOKEN: 'generic-token',
45
+ }, 'https://example.com/agent');
46
+ expect(result.apiKey).toBe('generic-key');
47
+ expect(result.bearerToken).toBe('generic-token');
48
+ });
49
+ it('handles URLs with ports correctly (port is not in hostname)', () => {
50
+ const result = resolveA2aCredentials({ A2A_API_KEY_LOCALHOST: 'local-key' }, 'http://localhost:8080/agent');
51
+ expect(result.apiKey).toBe('local-key');
52
+ });
53
+ it('handles hostnames with hyphens', () => {
54
+ const result = resolveA2aCredentials({ A2A_API_KEY_SPRING_AGENT_EXAMPLE_COM: 'spring-key' }, 'http://spring-agent.example.com:8080/');
55
+ expect(result.apiKey).toBe('spring-key');
56
+ });
57
+ it('handles hostnames with multiple dots and hyphens', () => {
58
+ const result = resolveA2aCredentials({ A2A_BEARER_TOKEN_MY_AGENT_US_EAST_1_EXAMPLE_IO: 'region-token' }, 'https://my-agent.us-east-1.example.io/a2a');
59
+ expect(result.bearerToken).toBe('region-token');
60
+ });
61
+ it('ignores invalid agentUrl and falls back to generic', () => {
62
+ const result = resolveA2aCredentials({ A2A_API_KEY: 'generic-key' }, 'not-a-url');
63
+ expect(result.apiKey).toBe('generic-key');
64
+ });
65
+ it('returns empty when agentUrl is invalid and no generic keys', () => {
66
+ const result = resolveA2aCredentials({}, 'not-a-url');
67
+ expect(result).toEqual({});
68
+ });
69
+ });
70
+ // ---------------------------------------------------------------------------
71
+ // buildA2aAuthHeaders
72
+ // ---------------------------------------------------------------------------
73
+ describe('buildA2aAuthHeaders', () => {
74
+ it('returns empty object when no credentials', () => {
75
+ const result = buildA2aAuthHeaders({});
76
+ expect(result).toEqual({});
77
+ });
78
+ it('sets bearer token in Authorization header (auto-detect)', () => {
79
+ const result = buildA2aAuthHeaders({ bearerToken: 'my-token' });
80
+ expect(result).toEqual({ Authorization: 'Bearer my-token' });
81
+ });
82
+ it('sets API key in x-api-key header (auto-detect)', () => {
83
+ const result = buildA2aAuthHeaders({ apiKey: 'my-key' });
84
+ expect(result).toEqual({ 'x-api-key': 'my-key' });
85
+ });
86
+ it('prefers bearer token over API key in auto-detect mode', () => {
87
+ const result = buildA2aAuthHeaders({
88
+ bearerToken: 'my-token',
89
+ apiKey: 'my-key',
90
+ });
91
+ expect(result).toEqual({ Authorization: 'Bearer my-token' });
92
+ });
93
+ it('respects apiKey auth scheme with custom header name', () => {
94
+ const schemes = [
95
+ { type: 'apiKey', in: 'header', name: 'X-Custom-Key' },
96
+ ];
97
+ const result = buildA2aAuthHeaders({ apiKey: 'my-key' }, schemes);
98
+ expect(result).toEqual({ 'X-Custom-Key': 'my-key' });
99
+ });
100
+ it('uses default x-api-key header when scheme has no name', () => {
101
+ const schemes = [
102
+ { type: 'apiKey', in: 'header' },
103
+ ];
104
+ const result = buildA2aAuthHeaders({ apiKey: 'my-key' }, schemes);
105
+ expect(result).toEqual({ 'x-api-key': 'my-key' });
106
+ });
107
+ it('respects http bearer auth scheme', () => {
108
+ const schemes = [
109
+ { type: 'http', scheme: 'bearer' },
110
+ ];
111
+ const result = buildA2aAuthHeaders({ bearerToken: 'my-token' }, schemes);
112
+ expect(result).toEqual({ Authorization: 'Bearer my-token' });
113
+ });
114
+ it('returns empty when scheme requires apiKey but only bearerToken provided', () => {
115
+ const schemes = [
116
+ { type: 'apiKey', in: 'header', name: 'x-api-key' },
117
+ ];
118
+ const result = buildA2aAuthHeaders({ bearerToken: 'my-token' }, schemes);
119
+ expect(result).toEqual({});
120
+ });
121
+ it('returns empty when scheme requires bearer but only apiKey provided', () => {
122
+ const schemes = [
123
+ { type: 'http', scheme: 'bearer' },
124
+ ];
125
+ const result = buildA2aAuthHeaders({ apiKey: 'my-key' }, schemes);
126
+ expect(result).toEqual({});
127
+ });
128
+ it('matches first compatible scheme when multiple provided', () => {
129
+ const schemes = [
130
+ { type: 'http', scheme: 'bearer' },
131
+ { type: 'apiKey', in: 'header', name: 'x-api-key' },
132
+ ];
133
+ const creds = { bearerToken: 'my-token', apiKey: 'my-key' };
134
+ const result = buildA2aAuthHeaders(creds, schemes);
135
+ // First scheme (bearer) should match
136
+ expect(result).toEqual({ Authorization: 'Bearer my-token' });
137
+ });
138
+ it('falls to second scheme when first does not match', () => {
139
+ const schemes = [
140
+ { type: 'http', scheme: 'bearer' },
141
+ { type: 'apiKey', in: 'header', name: 'x-api-key' },
142
+ ];
143
+ const creds = { apiKey: 'my-key' };
144
+ const result = buildA2aAuthHeaders(creds, schemes);
145
+ // Only apiKey scheme matches
146
+ expect(result).toEqual({ 'x-api-key': 'my-key' });
147
+ });
148
+ it('returns empty when auth schemes array is empty (treated as no schemes)', () => {
149
+ const result = buildA2aAuthHeaders({ apiKey: 'my-key' }, []);
150
+ // Empty array means no schemes -> auto-detect
151
+ expect(result).toEqual({ 'x-api-key': 'my-key' });
152
+ });
153
+ it('ignores unsupported oauth2 scheme and returns empty', () => {
154
+ const schemes = [
155
+ { type: 'oauth2' },
156
+ ];
157
+ const result = buildA2aAuthHeaders({ bearerToken: 'my-token' }, schemes);
158
+ expect(result).toEqual({});
159
+ });
160
+ });
161
+ // ---------------------------------------------------------------------------
162
+ // validateA2aAuth
163
+ // ---------------------------------------------------------------------------
164
+ describe('validateA2aAuth', () => {
165
+ const originalEnv = process.env;
166
+ beforeEach(() => {
167
+ process.env = { ...originalEnv };
168
+ // Clear relevant env vars
169
+ delete process.env.A2A_SERVER_API_KEY;
170
+ delete process.env.WORKER_API_KEY;
171
+ });
172
+ afterEach(() => {
173
+ process.env = originalEnv;
174
+ });
175
+ it('returns false when no auth header provided', () => {
176
+ expect(validateA2aAuth(undefined, { apiKey: 'expected' })).toBe(false);
177
+ });
178
+ it('returns false when no expected key configured', () => {
179
+ expect(validateA2aAuth('Bearer some-token')).toBe(false);
180
+ });
181
+ it('returns true for matching bearer token', () => {
182
+ const result = validateA2aAuth('Bearer secret-key', { apiKey: 'secret-key' });
183
+ expect(result).toBe(true);
184
+ });
185
+ it('returns true for matching raw API key (no Bearer prefix)', () => {
186
+ const result = validateA2aAuth('secret-key', { apiKey: 'secret-key' });
187
+ expect(result).toBe(true);
188
+ });
189
+ it('returns false for mismatched key (timing-safe)', () => {
190
+ const result = validateA2aAuth('Bearer wrong-key', { apiKey: 'correct-key' });
191
+ expect(result).toBe(false);
192
+ });
193
+ it('returns false for mismatched key of different length', () => {
194
+ const result = validateA2aAuth('Bearer short', { apiKey: 'a-much-longer-key' });
195
+ expect(result).toBe(false);
196
+ });
197
+ it('resolves expected key from A2A_SERVER_API_KEY env var', () => {
198
+ process.env.A2A_SERVER_API_KEY = 'env-key';
199
+ const result = validateA2aAuth('Bearer env-key');
200
+ expect(result).toBe(true);
201
+ });
202
+ it('falls back to WORKER_API_KEY env var', () => {
203
+ process.env.WORKER_API_KEY = 'worker-key';
204
+ const result = validateA2aAuth('Bearer worker-key');
205
+ expect(result).toBe(true);
206
+ });
207
+ it('A2A_SERVER_API_KEY takes precedence over WORKER_API_KEY', () => {
208
+ process.env.A2A_SERVER_API_KEY = 'a2a-key';
209
+ process.env.WORKER_API_KEY = 'worker-key';
210
+ expect(validateA2aAuth('Bearer a2a-key')).toBe(true);
211
+ expect(validateA2aAuth('Bearer worker-key')).toBe(false);
212
+ });
213
+ it('uses custom env var name from config', () => {
214
+ process.env.MY_CUSTOM_KEY = 'custom-value';
215
+ const result = validateA2aAuth('Bearer custom-value', {
216
+ apiKeyEnvVar: 'MY_CUSTOM_KEY',
217
+ });
218
+ expect(result).toBe(true);
219
+ // Clean up
220
+ delete process.env.MY_CUSTOM_KEY;
221
+ });
222
+ it('explicit apiKey in config takes precedence over env vars', () => {
223
+ process.env.A2A_SERVER_API_KEY = 'env-key';
224
+ const result = validateA2aAuth('Bearer explicit-key', { apiKey: 'explicit-key' });
225
+ expect(result).toBe(true);
226
+ });
227
+ it('returns false for empty auth header string', () => {
228
+ // extractToken with "Bearer " prefix but nothing after gives empty string
229
+ // but "" still gets compared — and empty provided vs non-empty expected -> false
230
+ expect(validateA2aAuth('', { apiKey: 'expected' })).toBe(false);
231
+ });
232
+ });
@@ -0,0 +1,254 @@
1
+ /**
2
+ * A2A (Agent-to-Agent) Client Provider
3
+ *
4
+ * Invokes external A2A-compliant agents over HTTP using the Google A2A protocol.
5
+ * Uses JSON-RPC 2.0 for requests and SSE streaming for responses.
6
+ *
7
+ * A2A Protocol overview:
8
+ * - Agent discovery via GET /.well-known/agent-card.json
9
+ * - Task creation via POST /a2a with JSON-RPC method "message/send" or "message/stream"
10
+ * - Task lifecycle: submitted → working → completed/failed/canceled
11
+ * - Input-required is a paused state requesting user input
12
+ * - SSE events: TaskStatusUpdateEvent, TaskArtifactUpdateEvent
13
+ *
14
+ * Env vars:
15
+ * A2A_AGENT_URL — base URL of the A2A agent (e.g., https://agent.example.com)
16
+ * A2A_AGENT_URL_{WORKTYPE} — work-type-specific override (e.g., A2A_AGENT_URL_RESEARCH)
17
+ * A2A_API_KEY — API key for authentication (sent as x-api-key header)
18
+ * A2A_BEARER_TOKEN — Bearer token for authentication (sent as Authorization header)
19
+ */
20
+ import type { AgentProvider, AgentSpawnConfig, AgentHandle, AgentEvent } from './types.js';
21
+ /** A2A message part: plain text */
22
+ export interface A2aTextPart {
23
+ /** Part type discriminator */
24
+ type: 'text';
25
+ /** The text content */
26
+ text: string;
27
+ }
28
+ /** A2A message part: file (inline or by URI) */
29
+ export interface A2aFilePart {
30
+ /** Part type discriminator */
31
+ type: 'file';
32
+ /** File metadata */
33
+ file: {
34
+ /** File name */
35
+ name?: string;
36
+ /** MIME type */
37
+ mimeType?: string;
38
+ /** Base64-encoded file content (mutually exclusive with uri) */
39
+ bytes?: string;
40
+ /** URI to the file (mutually exclusive with bytes) */
41
+ uri?: string;
42
+ };
43
+ }
44
+ /** A2A message part: structured data */
45
+ export interface A2aDataPart {
46
+ /** Part type discriminator */
47
+ type: 'data';
48
+ /** Arbitrary structured data */
49
+ data: Record<string, unknown>;
50
+ }
51
+ /** Union of all A2A message part types */
52
+ export type A2aPart = A2aTextPart | A2aFilePart | A2aDataPart;
53
+ /** A2A message with role and content parts */
54
+ export interface A2aMessage {
55
+ /** The role of the message sender */
56
+ role: 'user' | 'agent';
57
+ /** Content parts of the message */
58
+ parts: A2aPart[];
59
+ /** Optional metadata */
60
+ metadata?: Record<string, unknown>;
61
+ }
62
+ /** Task status values in the A2A protocol */
63
+ export type A2aTaskStatus = 'submitted' | 'working' | 'input-required' | 'completed' | 'failed' | 'canceled';
64
+ /** A2A task status object */
65
+ export interface A2aTaskStatusObject {
66
+ /** Current task state */
67
+ state: A2aTaskStatus;
68
+ /** Optional message associated with the status */
69
+ message?: A2aMessage;
70
+ /** Timestamp of the status update */
71
+ timestamp?: string;
72
+ }
73
+ /** A2A artifact produced by the agent */
74
+ export interface A2aArtifact {
75
+ /** Artifact name */
76
+ name?: string;
77
+ /** Artifact description */
78
+ description?: string;
79
+ /** Content parts of the artifact */
80
+ parts: A2aPart[];
81
+ /** Artifact index (for ordering) */
82
+ index?: number;
83
+ /** Whether this is the last chunk of a streaming artifact */
84
+ lastChunk?: boolean;
85
+ /** Optional metadata */
86
+ metadata?: Record<string, unknown>;
87
+ }
88
+ /** A2A task object */
89
+ export interface A2aTask {
90
+ /** Task identifier */
91
+ id: string;
92
+ /** Session identifier for multi-turn conversations */
93
+ sessionId?: string;
94
+ /** Current task status */
95
+ status: A2aTaskStatusObject;
96
+ /** Messages exchanged in the task */
97
+ messages?: A2aMessage[];
98
+ /** Artifacts produced by the agent */
99
+ artifacts?: A2aArtifact[];
100
+ /** Optional metadata */
101
+ metadata?: Record<string, unknown>;
102
+ }
103
+ /** SSE event: task status update */
104
+ export interface A2aTaskStatusUpdateEvent {
105
+ /** Event type discriminator */
106
+ type: 'TaskStatusUpdate';
107
+ /** Task ID */
108
+ id: string;
109
+ /** Session ID */
110
+ sessionId?: string;
111
+ /** Updated status */
112
+ status: A2aTaskStatusObject;
113
+ /** Whether this is the final event */
114
+ final?: boolean;
115
+ }
116
+ /** SSE event: task artifact update */
117
+ export interface A2aTaskArtifactUpdateEvent {
118
+ /** Event type discriminator */
119
+ type: 'TaskArtifactUpdate';
120
+ /** Task ID */
121
+ id: string;
122
+ /** Session ID */
123
+ sessionId?: string;
124
+ /** The artifact being updated */
125
+ artifact: A2aArtifact;
126
+ }
127
+ /** Union of all A2A SSE task event types */
128
+ export type A2aTaskEvent = A2aTaskStatusUpdateEvent | A2aTaskArtifactUpdateEvent;
129
+ /**
130
+ * A2A Agent Card — describes the agent's capabilities and metadata.
131
+ * Fetched from GET {baseUrl}/.well-known/agent-card.json
132
+ */
133
+ export interface A2aAgentCard {
134
+ /** Agent name */
135
+ name: string;
136
+ /** Human-readable description */
137
+ description?: string;
138
+ /** Base URL of the agent */
139
+ url: string;
140
+ /** Agent version */
141
+ version?: string;
142
+ /** Skills the agent supports */
143
+ skills?: A2aAgentSkill[];
144
+ /** Authentication schemes supported */
145
+ authSchemes?: A2aAuthScheme[];
146
+ /** Agent capabilities */
147
+ capabilities?: A2aAgentCapabilities;
148
+ /** Optional metadata */
149
+ metadata?: Record<string, unknown>;
150
+ }
151
+ /** A2A agent skill descriptor */
152
+ export interface A2aAgentSkill {
153
+ /** Skill identifier */
154
+ id: string;
155
+ /** Human-readable name */
156
+ name: string;
157
+ /** Description of the skill */
158
+ description?: string;
159
+ /** Input content types */
160
+ inputContentTypes?: string[];
161
+ /** Output content types */
162
+ outputContentTypes?: string[];
163
+ }
164
+ /** A2A authentication scheme */
165
+ export interface A2aAuthScheme {
166
+ /** Auth scheme type (e.g., "apiKey", "bearer", "oauth2") */
167
+ type: string;
168
+ /** Additional scheme-specific configuration */
169
+ [key: string]: unknown;
170
+ }
171
+ /** A2A agent capabilities */
172
+ export interface A2aAgentCapabilities {
173
+ /** Whether the agent supports SSE streaming */
174
+ streaming?: boolean;
175
+ /** Whether the agent supports push notifications */
176
+ pushNotifications?: boolean;
177
+ /** Whether the agent supports multi-turn conversations */
178
+ multiTurn?: boolean;
179
+ }
180
+ /** JSON-RPC 2.0 request */
181
+ export interface A2aJsonRpcRequest {
182
+ /** JSON-RPC version — always "2.0" */
183
+ jsonrpc: '2.0';
184
+ /** Request identifier */
185
+ id: string;
186
+ /** Method name */
187
+ method: string;
188
+ /** Method parameters */
189
+ params: Record<string, unknown>;
190
+ }
191
+ /** JSON-RPC 2.0 success response */
192
+ export interface A2aJsonRpcResponse {
193
+ /** JSON-RPC version — always "2.0" */
194
+ jsonrpc: '2.0';
195
+ /** Request identifier (matches request) */
196
+ id: string;
197
+ /** Result payload */
198
+ result?: unknown;
199
+ /** Error payload */
200
+ error?: A2aJsonRpcError;
201
+ }
202
+ /** JSON-RPC 2.0 error object */
203
+ export interface A2aJsonRpcError {
204
+ /** Error code */
205
+ code: number;
206
+ /** Error message */
207
+ message: string;
208
+ /** Optional error data */
209
+ data?: unknown;
210
+ }
211
+ /**
212
+ * Fetch and parse the A2A Agent Card from the well-known endpoint.
213
+ *
214
+ * @param baseUrl - Base URL of the A2A agent (e.g., "https://agent.example.com")
215
+ * @returns Parsed agent card
216
+ * @throws Error if the fetch fails or the response is not valid JSON
217
+ */
218
+ export declare function fetchAgentCard(baseUrl: string): Promise<A2aAgentCard>;
219
+ /**
220
+ * Mutable state tracked across A2A task events for a single session.
221
+ * Passed into mapA2aTaskEvent to accumulate session-level data.
222
+ */
223
+ export interface A2aEventMapperState {
224
+ /** Session ID from the A2A task */
225
+ sessionId: string | null;
226
+ /** Task ID from the A2A task */
227
+ taskId: string | null;
228
+ /** Accumulated input tokens */
229
+ totalInputTokens: number;
230
+ /** Accumulated output tokens */
231
+ totalOutputTokens: number;
232
+ /** Number of turns/interactions */
233
+ turnCount: number;
234
+ }
235
+ /**
236
+ * Map a single A2A task event to one or more normalized AgentEvents.
237
+ * Exported for unit testing — the AgentHandle uses this internally.
238
+ *
239
+ * @param event - The A2A SSE task event
240
+ * @param state - Mutable state accumulator for the session
241
+ * @returns Array of normalized AgentEvent objects
242
+ */
243
+ export declare function mapA2aTaskEvent(event: A2aTaskEvent, state: A2aEventMapperState): AgentEvent[];
244
+ export declare class A2aProvider implements AgentProvider {
245
+ readonly name: "a2a";
246
+ spawn(config: AgentSpawnConfig): AgentHandle;
247
+ resume(sessionId: string, config: AgentSpawnConfig): AgentHandle;
248
+ private createHandle;
249
+ }
250
+ /**
251
+ * Create a new A2A provider instance.
252
+ */
253
+ export declare function createA2aProvider(): A2aProvider;
254
+ //# sourceMappingURL=a2a-provider.d.ts.map