@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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=a2a-provider.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a2a-provider.test.d.ts","sourceRoot":"","sources":["../../../src/providers/a2a-provider.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,681 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { mapA2aTaskEvent, fetchAgentCard, } from './a2a-provider.js';
3
+ function freshState() {
4
+ return {
5
+ sessionId: null,
6
+ taskId: null,
7
+ totalInputTokens: 0,
8
+ totalOutputTokens: 0,
9
+ turnCount: 0,
10
+ };
11
+ }
12
+ describe('mapA2aTaskEvent', () => {
13
+ // -----------------------------------------------------------------------
14
+ // TaskStatusUpdate — submitted
15
+ // -----------------------------------------------------------------------
16
+ it('maps submitted status to init event', () => {
17
+ const state = freshState();
18
+ const event = {
19
+ type: 'TaskStatusUpdate',
20
+ id: 'task-123',
21
+ sessionId: 'sess-456',
22
+ status: { state: 'submitted' },
23
+ };
24
+ const result = mapA2aTaskEvent(event, state);
25
+ expect(result).toHaveLength(1);
26
+ expect(result[0]).toMatchObject({
27
+ type: 'init',
28
+ sessionId: 'sess-456',
29
+ });
30
+ expect(state.sessionId).toBe('sess-456');
31
+ expect(state.taskId).toBe('task-123');
32
+ });
33
+ it('uses task id as sessionId when sessionId is not present', () => {
34
+ const state = freshState();
35
+ const event = {
36
+ type: 'TaskStatusUpdate',
37
+ id: 'task-789',
38
+ status: { state: 'submitted' },
39
+ };
40
+ const result = mapA2aTaskEvent(event, state);
41
+ expect(result[0]).toMatchObject({
42
+ type: 'init',
43
+ sessionId: 'task-789',
44
+ });
45
+ expect(state.sessionId).toBe('task-789');
46
+ });
47
+ // -----------------------------------------------------------------------
48
+ // TaskStatusUpdate — working
49
+ // -----------------------------------------------------------------------
50
+ it('maps working status with message to assistant_text', () => {
51
+ const state = freshState();
52
+ const event = {
53
+ type: 'TaskStatusUpdate',
54
+ id: 'task-123',
55
+ status: {
56
+ state: 'working',
57
+ message: {
58
+ role: 'agent',
59
+ parts: [{ type: 'text', text: 'Analyzing the codebase...' }],
60
+ },
61
+ },
62
+ };
63
+ const result = mapA2aTaskEvent(event, state);
64
+ expect(result).toHaveLength(1);
65
+ expect(result[0]).toMatchObject({
66
+ type: 'assistant_text',
67
+ text: 'Analyzing the codebase...',
68
+ });
69
+ expect(state.turnCount).toBe(1);
70
+ });
71
+ it('maps working status without message to system event', () => {
72
+ const state = freshState();
73
+ const event = {
74
+ type: 'TaskStatusUpdate',
75
+ id: 'task-123',
76
+ status: { state: 'working' },
77
+ };
78
+ const result = mapA2aTaskEvent(event, state);
79
+ expect(result).toHaveLength(1);
80
+ expect(result[0]).toMatchObject({
81
+ type: 'system',
82
+ subtype: 'working',
83
+ message: 'Task task-123 is working (turn 1)',
84
+ });
85
+ expect(state.turnCount).toBe(1);
86
+ });
87
+ it('increments turn count on each working event', () => {
88
+ const state = freshState();
89
+ const event = {
90
+ type: 'TaskStatusUpdate',
91
+ id: 'task-123',
92
+ status: { state: 'working' },
93
+ };
94
+ mapA2aTaskEvent(event, state);
95
+ expect(state.turnCount).toBe(1);
96
+ mapA2aTaskEvent(event, state);
97
+ expect(state.turnCount).toBe(2);
98
+ const result = mapA2aTaskEvent(event, state);
99
+ expect(state.turnCount).toBe(3);
100
+ expect(result[0]).toMatchObject({
101
+ message: 'Task task-123 is working (turn 3)',
102
+ });
103
+ });
104
+ // -----------------------------------------------------------------------
105
+ // TaskStatusUpdate — input-required
106
+ // -----------------------------------------------------------------------
107
+ it('maps input-required status to system event with input_required subtype', () => {
108
+ const state = freshState();
109
+ const event = {
110
+ type: 'TaskStatusUpdate',
111
+ id: 'task-123',
112
+ status: {
113
+ state: 'input-required',
114
+ message: {
115
+ role: 'agent',
116
+ parts: [{ type: 'text', text: 'Please provide the API key' }],
117
+ },
118
+ },
119
+ };
120
+ const result = mapA2aTaskEvent(event, state);
121
+ expect(result).toHaveLength(1);
122
+ expect(result[0]).toMatchObject({
123
+ type: 'system',
124
+ subtype: 'input_required',
125
+ message: 'Please provide the API key',
126
+ });
127
+ });
128
+ it('maps input-required without message to default message', () => {
129
+ const state = freshState();
130
+ const event = {
131
+ type: 'TaskStatusUpdate',
132
+ id: 'task-123',
133
+ status: { state: 'input-required' },
134
+ };
135
+ const result = mapA2aTaskEvent(event, state);
136
+ expect(result[0]).toMatchObject({
137
+ type: 'system',
138
+ subtype: 'input_required',
139
+ message: 'Agent requires additional input',
140
+ });
141
+ });
142
+ // -----------------------------------------------------------------------
143
+ // TaskStatusUpdate — completed
144
+ // -----------------------------------------------------------------------
145
+ it('maps completed status to success result', () => {
146
+ const state = freshState();
147
+ state.turnCount = 3;
148
+ state.totalInputTokens = 500;
149
+ state.totalOutputTokens = 200;
150
+ const event = {
151
+ type: 'TaskStatusUpdate',
152
+ id: 'task-123',
153
+ status: {
154
+ state: 'completed',
155
+ message: {
156
+ role: 'agent',
157
+ parts: [{ type: 'text', text: 'All done!' }],
158
+ },
159
+ },
160
+ };
161
+ const result = mapA2aTaskEvent(event, state);
162
+ expect(result).toHaveLength(1);
163
+ expect(result[0]).toMatchObject({
164
+ type: 'result',
165
+ success: true,
166
+ message: 'All done!',
167
+ cost: {
168
+ inputTokens: 500,
169
+ outputTokens: 200,
170
+ numTurns: 3,
171
+ },
172
+ });
173
+ });
174
+ it('maps completed status without message', () => {
175
+ const state = freshState();
176
+ const event = {
177
+ type: 'TaskStatusUpdate',
178
+ id: 'task-123',
179
+ status: { state: 'completed' },
180
+ };
181
+ const result = mapA2aTaskEvent(event, state);
182
+ expect(result[0]).toMatchObject({
183
+ type: 'result',
184
+ success: true,
185
+ });
186
+ expect(result[0].message).toBeUndefined();
187
+ });
188
+ it('omits zero-value cost fields', () => {
189
+ const state = freshState();
190
+ const event = {
191
+ type: 'TaskStatusUpdate',
192
+ id: 'task-123',
193
+ status: { state: 'completed' },
194
+ };
195
+ const result = mapA2aTaskEvent(event, state);
196
+ expect(result[0].cost).toEqual({
197
+ inputTokens: undefined,
198
+ outputTokens: undefined,
199
+ numTurns: undefined,
200
+ });
201
+ });
202
+ // -----------------------------------------------------------------------
203
+ // TaskStatusUpdate — failed
204
+ // -----------------------------------------------------------------------
205
+ it('maps failed status to failure result', () => {
206
+ const state = freshState();
207
+ const event = {
208
+ type: 'TaskStatusUpdate',
209
+ id: 'task-123',
210
+ status: {
211
+ state: 'failed',
212
+ message: {
213
+ role: 'agent',
214
+ parts: [{ type: 'text', text: 'Model rate limit exceeded' }],
215
+ },
216
+ },
217
+ };
218
+ const result = mapA2aTaskEvent(event, state);
219
+ expect(result).toHaveLength(1);
220
+ expect(result[0]).toMatchObject({
221
+ type: 'result',
222
+ success: false,
223
+ errors: ['Model rate limit exceeded'],
224
+ errorSubtype: 'task_failed',
225
+ });
226
+ });
227
+ it('maps failed status without message to default error', () => {
228
+ const state = freshState();
229
+ const event = {
230
+ type: 'TaskStatusUpdate',
231
+ id: 'task-123',
232
+ status: { state: 'failed' },
233
+ };
234
+ const result = mapA2aTaskEvent(event, state);
235
+ expect(result[0]).toMatchObject({
236
+ type: 'result',
237
+ success: false,
238
+ errors: ['Task failed'],
239
+ errorSubtype: 'task_failed',
240
+ });
241
+ });
242
+ // -----------------------------------------------------------------------
243
+ // TaskStatusUpdate — canceled
244
+ // -----------------------------------------------------------------------
245
+ it('maps canceled status to failure result with canceled subtype', () => {
246
+ const state = freshState();
247
+ const event = {
248
+ type: 'TaskStatusUpdate',
249
+ id: 'task-123',
250
+ status: {
251
+ state: 'canceled',
252
+ message: {
253
+ role: 'agent',
254
+ parts: [{ type: 'text', text: 'User requested cancellation' }],
255
+ },
256
+ },
257
+ };
258
+ const result = mapA2aTaskEvent(event, state);
259
+ expect(result).toHaveLength(1);
260
+ expect(result[0]).toMatchObject({
261
+ type: 'result',
262
+ success: false,
263
+ errors: ['User requested cancellation'],
264
+ errorSubtype: 'canceled',
265
+ });
266
+ });
267
+ it('maps canceled status without message to default', () => {
268
+ const state = freshState();
269
+ const event = {
270
+ type: 'TaskStatusUpdate',
271
+ id: 'task-123',
272
+ status: { state: 'canceled' },
273
+ };
274
+ const result = mapA2aTaskEvent(event, state);
275
+ expect(result[0]).toMatchObject({
276
+ type: 'result',
277
+ success: false,
278
+ errors: ['Task canceled'],
279
+ errorSubtype: 'canceled',
280
+ });
281
+ });
282
+ // -----------------------------------------------------------------------
283
+ // TaskStatusUpdate — unknown state
284
+ // -----------------------------------------------------------------------
285
+ it('handles unknown status state gracefully', () => {
286
+ const state = freshState();
287
+ const event = {
288
+ type: 'TaskStatusUpdate',
289
+ id: 'task-123',
290
+ status: { state: 'paused' },
291
+ };
292
+ const result = mapA2aTaskEvent(event, state);
293
+ expect(result[0]).toMatchObject({
294
+ type: 'system',
295
+ subtype: 'unknown',
296
+ message: 'Unknown A2A task status: paused',
297
+ });
298
+ });
299
+ // -----------------------------------------------------------------------
300
+ // TaskArtifactUpdate
301
+ // -----------------------------------------------------------------------
302
+ it('maps artifact update with text parts to assistant_text', () => {
303
+ const state = freshState();
304
+ const event = {
305
+ type: 'TaskArtifactUpdate',
306
+ id: 'task-123',
307
+ artifact: {
308
+ name: 'analysis-report',
309
+ description: 'Code analysis report',
310
+ parts: [{ type: 'text', text: 'Found 3 issues in the codebase.' }],
311
+ },
312
+ };
313
+ const result = mapA2aTaskEvent(event, state);
314
+ expect(result).toHaveLength(1);
315
+ expect(result[0]).toMatchObject({
316
+ type: 'assistant_text',
317
+ text: 'Found 3 issues in the codebase.',
318
+ });
319
+ });
320
+ it('maps artifact update with data parts to JSON text', () => {
321
+ const state = freshState();
322
+ const event = {
323
+ type: 'TaskArtifactUpdate',
324
+ id: 'task-123',
325
+ artifact: {
326
+ parts: [{ type: 'data', data: { issues: 3, severity: 'high' } }],
327
+ },
328
+ };
329
+ const result = mapA2aTaskEvent(event, state);
330
+ expect(result[0]).toMatchObject({
331
+ type: 'assistant_text',
332
+ text: '{"issues":3,"severity":"high"}',
333
+ });
334
+ });
335
+ it('maps artifact update with file parts', () => {
336
+ const state = freshState();
337
+ const event = {
338
+ type: 'TaskArtifactUpdate',
339
+ id: 'task-123',
340
+ artifact: {
341
+ parts: [{ type: 'file', file: { name: 'report.pdf', mimeType: 'application/pdf' } }],
342
+ },
343
+ };
344
+ const result = mapA2aTaskEvent(event, state);
345
+ expect(result[0]).toMatchObject({
346
+ type: 'assistant_text',
347
+ text: '[File: report.pdf]',
348
+ });
349
+ });
350
+ it('maps artifact update with empty parts to artifact name', () => {
351
+ const state = freshState();
352
+ const event = {
353
+ type: 'TaskArtifactUpdate',
354
+ id: 'task-123',
355
+ artifact: {
356
+ name: 'my-artifact',
357
+ parts: [],
358
+ },
359
+ };
360
+ const result = mapA2aTaskEvent(event, state);
361
+ expect(result[0]).toMatchObject({
362
+ type: 'assistant_text',
363
+ text: '[Artifact: my-artifact]',
364
+ });
365
+ });
366
+ it('maps artifact update with unnamed empty parts', () => {
367
+ const state = freshState();
368
+ const event = {
369
+ type: 'TaskArtifactUpdate',
370
+ id: 'task-123',
371
+ artifact: {
372
+ parts: [],
373
+ },
374
+ };
375
+ const result = mapA2aTaskEvent(event, state);
376
+ expect(result[0]).toMatchObject({
377
+ type: 'assistant_text',
378
+ text: '[Artifact: unnamed]',
379
+ });
380
+ });
381
+ // -----------------------------------------------------------------------
382
+ // State tracking
383
+ // -----------------------------------------------------------------------
384
+ it('tracks taskId from first event', () => {
385
+ const state = freshState();
386
+ const event = {
387
+ type: 'TaskStatusUpdate',
388
+ id: 'task-abc',
389
+ status: { state: 'submitted' },
390
+ };
391
+ mapA2aTaskEvent(event, state);
392
+ expect(state.taskId).toBe('task-abc');
393
+ });
394
+ it('tracks sessionId from first event', () => {
395
+ const state = freshState();
396
+ const event = {
397
+ type: 'TaskStatusUpdate',
398
+ id: 'task-abc',
399
+ sessionId: 'sess-xyz',
400
+ status: { state: 'submitted' },
401
+ };
402
+ mapA2aTaskEvent(event, state);
403
+ expect(state.sessionId).toBe('sess-xyz');
404
+ });
405
+ it('does not overwrite taskId once set', () => {
406
+ const state = freshState();
407
+ state.taskId = 'existing-task';
408
+ const event = {
409
+ type: 'TaskArtifactUpdate',
410
+ id: 'new-task',
411
+ artifact: {
412
+ parts: [{ type: 'text', text: 'hello' }],
413
+ },
414
+ };
415
+ mapA2aTaskEvent(event, state);
416
+ expect(state.taskId).toBe('existing-task');
417
+ });
418
+ it('does not overwrite sessionId once set', () => {
419
+ const state = freshState();
420
+ state.sessionId = 'existing-session';
421
+ const event = {
422
+ type: 'TaskArtifactUpdate',
423
+ id: 'task-123',
424
+ sessionId: 'new-session',
425
+ artifact: {
426
+ parts: [{ type: 'text', text: 'hello' }],
427
+ },
428
+ };
429
+ mapA2aTaskEvent(event, state);
430
+ expect(state.sessionId).toBe('existing-session');
431
+ });
432
+ // -----------------------------------------------------------------------
433
+ // Unknown event type
434
+ // -----------------------------------------------------------------------
435
+ it('handles unknown event type gracefully', () => {
436
+ const state = freshState();
437
+ const event = { type: 'TaskSomethingElse', id: 'task-1' };
438
+ const result = mapA2aTaskEvent(event, state);
439
+ expect(result[0]).toMatchObject({
440
+ type: 'system',
441
+ subtype: 'unknown',
442
+ });
443
+ });
444
+ // -----------------------------------------------------------------------
445
+ // Multi-part messages
446
+ // -----------------------------------------------------------------------
447
+ it('concatenates multiple text parts', () => {
448
+ const state = freshState();
449
+ const event = {
450
+ type: 'TaskStatusUpdate',
451
+ id: 'task-123',
452
+ status: {
453
+ state: 'working',
454
+ message: {
455
+ role: 'agent',
456
+ parts: [
457
+ { type: 'text', text: 'Part 1' },
458
+ { type: 'text', text: 'Part 2' },
459
+ ],
460
+ },
461
+ },
462
+ };
463
+ const result = mapA2aTaskEvent(event, state);
464
+ expect(result[0]).toMatchObject({
465
+ type: 'assistant_text',
466
+ text: 'Part 1\nPart 2',
467
+ });
468
+ });
469
+ it('mixes text and data parts in message', () => {
470
+ const state = freshState();
471
+ const event = {
472
+ type: 'TaskStatusUpdate',
473
+ id: 'task-123',
474
+ status: {
475
+ state: 'working',
476
+ message: {
477
+ role: 'agent',
478
+ parts: [
479
+ { type: 'text', text: 'Results:' },
480
+ { type: 'data', data: { count: 42 } },
481
+ ],
482
+ },
483
+ },
484
+ };
485
+ const result = mapA2aTaskEvent(event, state);
486
+ expect(result[0]).toMatchObject({
487
+ type: 'assistant_text',
488
+ text: 'Results:\n{"count":42}',
489
+ });
490
+ });
491
+ // -----------------------------------------------------------------------
492
+ // raw event preservation
493
+ // -----------------------------------------------------------------------
494
+ it('preserves the raw event on all mapped events', () => {
495
+ const state = freshState();
496
+ const event = {
497
+ type: 'TaskStatusUpdate',
498
+ id: 'task-123',
499
+ status: { state: 'submitted' },
500
+ };
501
+ const result = mapA2aTaskEvent(event, state);
502
+ expect(result[0].raw).toBe(event);
503
+ });
504
+ });
505
+ // ---------------------------------------------------------------------------
506
+ // fetchAgentCard
507
+ // ---------------------------------------------------------------------------
508
+ describe('fetchAgentCard', () => {
509
+ const originalFetch = globalThis.fetch;
510
+ beforeEach(() => {
511
+ vi.restoreAllMocks();
512
+ });
513
+ afterEach(() => {
514
+ globalThis.fetch = originalFetch;
515
+ });
516
+ it('fetches and parses a valid agent card', async () => {
517
+ const card = {
518
+ name: 'Test Agent',
519
+ url: 'https://agent.example.com',
520
+ description: 'A test A2A agent',
521
+ version: '1.0.0',
522
+ skills: [
523
+ { id: 'code-review', name: 'Code Review', description: 'Reviews code' },
524
+ ],
525
+ capabilities: {
526
+ streaming: true,
527
+ multiTurn: true,
528
+ },
529
+ };
530
+ globalThis.fetch = vi.fn().mockResolvedValue({
531
+ ok: true,
532
+ json: () => Promise.resolve(card),
533
+ });
534
+ const result = await fetchAgentCard('https://agent.example.com');
535
+ expect(result).toEqual(card);
536
+ expect(globalThis.fetch).toHaveBeenCalledWith('https://agent.example.com/.well-known/agent-card.json', expect.objectContaining({
537
+ method: 'GET',
538
+ headers: { 'Accept': 'application/json' },
539
+ }));
540
+ });
541
+ it('strips trailing slashes from base URL', async () => {
542
+ const card = {
543
+ name: 'Test Agent',
544
+ url: 'https://agent.example.com',
545
+ };
546
+ globalThis.fetch = vi.fn().mockResolvedValue({
547
+ ok: true,
548
+ json: () => Promise.resolve(card),
549
+ });
550
+ await fetchAgentCard('https://agent.example.com/');
551
+ expect(globalThis.fetch).toHaveBeenCalledWith('https://agent.example.com/.well-known/agent-card.json', expect.anything());
552
+ });
553
+ it('throws on HTTP error', async () => {
554
+ globalThis.fetch = vi.fn().mockResolvedValue({
555
+ ok: false,
556
+ status: 404,
557
+ statusText: 'Not Found',
558
+ });
559
+ await expect(fetchAgentCard('https://agent.example.com'))
560
+ .rejects.toThrow('Failed to fetch A2A agent card');
561
+ });
562
+ it('throws on invalid agent card (missing name)', async () => {
563
+ globalThis.fetch = vi.fn().mockResolvedValue({
564
+ ok: true,
565
+ json: () => Promise.resolve({ url: 'https://agent.example.com' }),
566
+ });
567
+ await expect(fetchAgentCard('https://agent.example.com'))
568
+ .rejects.toThrow('missing required fields');
569
+ });
570
+ it('throws on invalid agent card (missing url)', async () => {
571
+ globalThis.fetch = vi.fn().mockResolvedValue({
572
+ ok: true,
573
+ json: () => Promise.resolve({ name: 'Test' }),
574
+ });
575
+ await expect(fetchAgentCard('https://agent.example.com'))
576
+ .rejects.toThrow('missing required fields');
577
+ });
578
+ });
579
+ // ---------------------------------------------------------------------------
580
+ // A2aProvider class and factory
581
+ // ---------------------------------------------------------------------------
582
+ describe('A2aProvider', () => {
583
+ it('exports A2aProvider class', async () => {
584
+ const { A2aProvider } = await import('./a2a-provider.js');
585
+ const provider = new A2aProvider();
586
+ expect(provider.name).toBe('a2a');
587
+ });
588
+ it('exports createA2aProvider factory', async () => {
589
+ const { createA2aProvider } = await import('./a2a-provider.js');
590
+ const provider = createA2aProvider();
591
+ expect(provider.name).toBe('a2a');
592
+ });
593
+ it('spawn returns a handle with error when A2A_AGENT_URL is not set', async () => {
594
+ const { A2aProvider } = await import('./a2a-provider.js');
595
+ const provider = new A2aProvider();
596
+ // Save and clear env
597
+ const savedUrl = process.env.A2A_AGENT_URL;
598
+ delete process.env.A2A_AGENT_URL;
599
+ // Clear any A2A_AGENT_URL_* vars
600
+ const savedUrlVars = {};
601
+ for (const key of Object.keys(process.env)) {
602
+ if (key.startsWith('A2A_AGENT_URL_')) {
603
+ savedUrlVars[key] = process.env[key];
604
+ delete process.env[key];
605
+ }
606
+ }
607
+ try {
608
+ const handle = provider.spawn({
609
+ prompt: 'test',
610
+ cwd: '/tmp',
611
+ env: {},
612
+ abortController: new AbortController(),
613
+ autonomous: true,
614
+ sandboxEnabled: false,
615
+ });
616
+ expect(handle.sessionId).toBeNull();
617
+ // Consume the stream to check error events
618
+ const events = [];
619
+ for await (const event of handle.stream) {
620
+ events.push(event);
621
+ }
622
+ expect(events).toHaveLength(2);
623
+ expect(events[0]).toMatchObject({
624
+ type: 'error',
625
+ message: expect.stringContaining('A2A_AGENT_URL'),
626
+ });
627
+ expect(events[1]).toMatchObject({
628
+ type: 'result',
629
+ success: false,
630
+ errorSubtype: 'configuration_error',
631
+ });
632
+ }
633
+ finally {
634
+ // Restore env
635
+ if (savedUrl !== undefined) {
636
+ process.env.A2A_AGENT_URL = savedUrl;
637
+ }
638
+ for (const [key, value] of Object.entries(savedUrlVars)) {
639
+ if (value !== undefined) {
640
+ process.env[key] = value;
641
+ }
642
+ }
643
+ }
644
+ });
645
+ it('resume sets sessionId on the handle', async () => {
646
+ const { A2aProvider } = await import('./a2a-provider.js');
647
+ const provider = new A2aProvider();
648
+ // Save and clear env
649
+ const savedUrl = process.env.A2A_AGENT_URL;
650
+ delete process.env.A2A_AGENT_URL;
651
+ const savedUrlVars = {};
652
+ for (const key of Object.keys(process.env)) {
653
+ if (key.startsWith('A2A_AGENT_URL_')) {
654
+ savedUrlVars[key] = process.env[key];
655
+ delete process.env[key];
656
+ }
657
+ }
658
+ try {
659
+ const handle = provider.spawn({
660
+ prompt: 'test',
661
+ cwd: '/tmp',
662
+ env: { A2A_AGENT_URL: 'https://agent.example.com' },
663
+ abortController: new AbortController(),
664
+ autonomous: true,
665
+ sandboxEnabled: false,
666
+ });
667
+ // When the URL is set, sessionId starts null (until init event)
668
+ expect(handle.sessionId).toBeNull();
669
+ }
670
+ finally {
671
+ if (savedUrl !== undefined) {
672
+ process.env.A2A_AGENT_URL = savedUrl;
673
+ }
674
+ for (const [key, value] of Object.entries(savedUrlVars)) {
675
+ if (value !== undefined) {
676
+ process.env[key] = value;
677
+ }
678
+ }
679
+ }
680
+ });
681
+ });