@yasserkhanorg/e2e-agents 0.3.2

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 (221) hide show
  1. package/LICENSE +168 -0
  2. package/README.md +620 -0
  3. package/dist/agent/analysis.d.ts +62 -0
  4. package/dist/agent/analysis.d.ts.map +1 -0
  5. package/dist/agent/analysis.js +292 -0
  6. package/dist/agent/blast_radius.d.ts +4 -0
  7. package/dist/agent/blast_radius.d.ts.map +1 -0
  8. package/dist/agent/blast_radius.js +37 -0
  9. package/dist/agent/cache_utils.d.ts +38 -0
  10. package/dist/agent/cache_utils.d.ts.map +1 -0
  11. package/dist/agent/cache_utils.js +67 -0
  12. package/dist/agent/config.d.ts +148 -0
  13. package/dist/agent/config.d.ts.map +1 -0
  14. package/dist/agent/config.js +640 -0
  15. package/dist/agent/dependency_graph.d.ts +14 -0
  16. package/dist/agent/dependency_graph.d.ts.map +1 -0
  17. package/dist/agent/dependency_graph.js +227 -0
  18. package/dist/agent/feedback.d.ts +55 -0
  19. package/dist/agent/feedback.d.ts.map +1 -0
  20. package/dist/agent/feedback.js +257 -0
  21. package/dist/agent/flags.d.ts +23 -0
  22. package/dist/agent/flags.d.ts.map +1 -0
  23. package/dist/agent/flags.js +171 -0
  24. package/dist/agent/flow_catalog.d.ts +25 -0
  25. package/dist/agent/flow_catalog.d.ts.map +1 -0
  26. package/dist/agent/flow_catalog.js +106 -0
  27. package/dist/agent/flow_mapping.d.ts +10 -0
  28. package/dist/agent/flow_mapping.d.ts.map +1 -0
  29. package/dist/agent/flow_mapping.js +84 -0
  30. package/dist/agent/framework.d.ts +13 -0
  31. package/dist/agent/framework.d.ts.map +1 -0
  32. package/dist/agent/framework.js +149 -0
  33. package/dist/agent/gap_suggestions.d.ts +14 -0
  34. package/dist/agent/gap_suggestions.d.ts.map +1 -0
  35. package/dist/agent/gap_suggestions.js +101 -0
  36. package/dist/agent/generator.d.ts +10 -0
  37. package/dist/agent/generator.d.ts.map +1 -0
  38. package/dist/agent/generator.js +115 -0
  39. package/dist/agent/git.d.ts +11 -0
  40. package/dist/agent/git.d.ts.map +1 -0
  41. package/dist/agent/git.js +90 -0
  42. package/dist/agent/handoff.d.ts +22 -0
  43. package/dist/agent/handoff.d.ts.map +1 -0
  44. package/dist/agent/handoff.js +180 -0
  45. package/dist/agent/impact-analyzer.d.ts +114 -0
  46. package/dist/agent/impact-analyzer.d.ts.map +1 -0
  47. package/dist/agent/impact-analyzer.js +557 -0
  48. package/dist/agent/index.d.ts +21 -0
  49. package/dist/agent/index.d.ts.map +1 -0
  50. package/dist/agent/index.js +38 -0
  51. package/dist/agent/model-router.d.ts +57 -0
  52. package/dist/agent/model-router.d.ts.map +1 -0
  53. package/dist/agent/model-router.js +154 -0
  54. package/dist/agent/operational_insights.d.ts +41 -0
  55. package/dist/agent/operational_insights.d.ts.map +1 -0
  56. package/dist/agent/operational_insights.js +126 -0
  57. package/dist/agent/pipeline.d.ts +23 -0
  58. package/dist/agent/pipeline.d.ts.map +1 -0
  59. package/dist/agent/pipeline.js +609 -0
  60. package/dist/agent/plan.d.ts +91 -0
  61. package/dist/agent/plan.d.ts.map +1 -0
  62. package/dist/agent/plan.js +331 -0
  63. package/dist/agent/playwright_report.d.ts +8 -0
  64. package/dist/agent/playwright_report.d.ts.map +1 -0
  65. package/dist/agent/playwright_report.js +126 -0
  66. package/dist/agent/report-generator.d.ts +24 -0
  67. package/dist/agent/report-generator.d.ts.map +1 -0
  68. package/dist/agent/report-generator.js +250 -0
  69. package/dist/agent/report.d.ts +81 -0
  70. package/dist/agent/report.d.ts.map +1 -0
  71. package/dist/agent/report.js +147 -0
  72. package/dist/agent/runner.d.ts +7 -0
  73. package/dist/agent/runner.d.ts.map +1 -0
  74. package/dist/agent/runner.js +576 -0
  75. package/dist/agent/selectors.d.ts +10 -0
  76. package/dist/agent/selectors.d.ts.map +1 -0
  77. package/dist/agent/selectors.js +75 -0
  78. package/dist/agent/spec-bridge.d.ts +101 -0
  79. package/dist/agent/spec-bridge.d.ts.map +1 -0
  80. package/dist/agent/spec-bridge.js +273 -0
  81. package/dist/agent/spec-builder.d.ts +102 -0
  82. package/dist/agent/spec-builder.d.ts.map +1 -0
  83. package/dist/agent/spec-builder.js +273 -0
  84. package/dist/agent/subsystem_risk.d.ts +23 -0
  85. package/dist/agent/subsystem_risk.d.ts.map +1 -0
  86. package/dist/agent/subsystem_risk.js +207 -0
  87. package/dist/agent/telemetry.d.ts +84 -0
  88. package/dist/agent/telemetry.d.ts.map +1 -0
  89. package/dist/agent/telemetry.js +220 -0
  90. package/dist/agent/test_path.d.ts +2 -0
  91. package/dist/agent/test_path.d.ts.map +1 -0
  92. package/dist/agent/test_path.js +23 -0
  93. package/dist/agent/tests.d.ts +18 -0
  94. package/dist/agent/tests.d.ts.map +1 -0
  95. package/dist/agent/tests.js +106 -0
  96. package/dist/agent/traceability.d.ts +22 -0
  97. package/dist/agent/traceability.d.ts.map +1 -0
  98. package/dist/agent/traceability.js +183 -0
  99. package/dist/agent/traceability_capture.d.ts +18 -0
  100. package/dist/agent/traceability_capture.d.ts.map +1 -0
  101. package/dist/agent/traceability_capture.js +313 -0
  102. package/dist/agent/traceability_ingest.d.ts +21 -0
  103. package/dist/agent/traceability_ingest.d.ts.map +1 -0
  104. package/dist/agent/traceability_ingest.js +237 -0
  105. package/dist/agent/utils.d.ts +13 -0
  106. package/dist/agent/utils.d.ts.map +1 -0
  107. package/dist/agent/utils.js +152 -0
  108. package/dist/agent/validators/selector-validator.d.ts +74 -0
  109. package/dist/agent/validators/selector-validator.d.ts.map +1 -0
  110. package/dist/agent/validators/selector-validator.js +165 -0
  111. package/dist/anthropic_provider.d.ts +65 -0
  112. package/dist/anthropic_provider.d.ts.map +1 -0
  113. package/dist/anthropic_provider.js +332 -0
  114. package/dist/api.d.ts +48 -0
  115. package/dist/api.d.ts.map +1 -0
  116. package/dist/api.js +113 -0
  117. package/dist/base_provider.d.ts +53 -0
  118. package/dist/base_provider.d.ts.map +1 -0
  119. package/dist/base_provider.js +81 -0
  120. package/dist/cli.d.ts +3 -0
  121. package/dist/cli.d.ts.map +1 -0
  122. package/dist/cli.js +843 -0
  123. package/dist/custom_provider.d.ts +20 -0
  124. package/dist/custom_provider.d.ts.map +1 -0
  125. package/dist/custom_provider.js +276 -0
  126. package/dist/e2e-test-gen/index.d.ts +51 -0
  127. package/dist/e2e-test-gen/index.d.ts.map +1 -0
  128. package/dist/e2e-test-gen/index.js +57 -0
  129. package/dist/e2e-test-gen/spec_parser.d.ts +142 -0
  130. package/dist/e2e-test-gen/spec_parser.d.ts.map +1 -0
  131. package/dist/e2e-test-gen/spec_parser.js +786 -0
  132. package/dist/e2e-test-gen/types.d.ts +185 -0
  133. package/dist/e2e-test-gen/types.d.ts.map +1 -0
  134. package/dist/e2e-test-gen/types.js +4 -0
  135. package/dist/esm/agent/analysis.js +287 -0
  136. package/dist/esm/agent/blast_radius.js +34 -0
  137. package/dist/esm/agent/cache_utils.js +63 -0
  138. package/dist/esm/agent/config.js +637 -0
  139. package/dist/esm/agent/dependency_graph.js +224 -0
  140. package/dist/esm/agent/feedback.js +253 -0
  141. package/dist/esm/agent/flags.js +160 -0
  142. package/dist/esm/agent/flow_catalog.js +103 -0
  143. package/dist/esm/agent/flow_mapping.js +81 -0
  144. package/dist/esm/agent/framework.js +145 -0
  145. package/dist/esm/agent/gap_suggestions.js +98 -0
  146. package/dist/esm/agent/generator.js +112 -0
  147. package/dist/esm/agent/git.js +87 -0
  148. package/dist/esm/agent/handoff.js +177 -0
  149. package/dist/esm/agent/impact-analyzer.js +548 -0
  150. package/dist/esm/agent/index.js +22 -0
  151. package/dist/esm/agent/model-router.js +150 -0
  152. package/dist/esm/agent/operational_insights.js +123 -0
  153. package/dist/esm/agent/pipeline.js +605 -0
  154. package/dist/esm/agent/plan.js +324 -0
  155. package/dist/esm/agent/playwright_report.js +123 -0
  156. package/dist/esm/agent/report-generator.js +247 -0
  157. package/dist/esm/agent/report.js +144 -0
  158. package/dist/esm/agent/runner.js +572 -0
  159. package/dist/esm/agent/selectors.js +71 -0
  160. package/dist/esm/agent/spec-bridge.js +267 -0
  161. package/dist/esm/agent/spec-builder.js +267 -0
  162. package/dist/esm/agent/subsystem_risk.js +204 -0
  163. package/dist/esm/agent/telemetry.js +216 -0
  164. package/dist/esm/agent/test_path.js +20 -0
  165. package/dist/esm/agent/tests.js +101 -0
  166. package/dist/esm/agent/traceability.js +180 -0
  167. package/dist/esm/agent/traceability_capture.js +310 -0
  168. package/dist/esm/agent/traceability_ingest.js +234 -0
  169. package/dist/esm/agent/utils.js +138 -0
  170. package/dist/esm/agent/validators/selector-validator.js +160 -0
  171. package/dist/esm/anthropic_provider.js +324 -0
  172. package/dist/esm/api.js +105 -0
  173. package/dist/esm/base_provider.js +77 -0
  174. package/dist/esm/cli.js +841 -0
  175. package/dist/esm/custom_provider.js +272 -0
  176. package/dist/esm/e2e-test-gen/index.js +50 -0
  177. package/dist/esm/e2e-test-gen/spec_parser.js +782 -0
  178. package/dist/esm/e2e-test-gen/types.js +3 -0
  179. package/dist/esm/index.js +16 -0
  180. package/dist/esm/logger.js +89 -0
  181. package/dist/esm/mcp-server.js +465 -0
  182. package/dist/esm/ollama_provider.js +300 -0
  183. package/dist/esm/openai_provider.js +242 -0
  184. package/dist/esm/package.json +3 -0
  185. package/dist/esm/plan-and-test-constants.js +126 -0
  186. package/dist/esm/provider_factory.js +336 -0
  187. package/dist/esm/provider_interface.js +23 -0
  188. package/dist/esm/provider_utils.js +96 -0
  189. package/dist/index.d.ts +31 -0
  190. package/dist/index.d.ts.map +1 -0
  191. package/dist/index.js +41 -0
  192. package/dist/logger.d.ts +23 -0
  193. package/dist/logger.d.ts.map +1 -0
  194. package/dist/logger.js +93 -0
  195. package/dist/mcp-server.d.ts +35 -0
  196. package/dist/mcp-server.d.ts.map +1 -0
  197. package/dist/mcp-server.js +469 -0
  198. package/dist/ollama_provider.d.ts +65 -0
  199. package/dist/ollama_provider.d.ts.map +1 -0
  200. package/dist/ollama_provider.js +308 -0
  201. package/dist/openai_provider.d.ts +23 -0
  202. package/dist/openai_provider.d.ts.map +1 -0
  203. package/dist/openai_provider.js +250 -0
  204. package/dist/plan-and-test-constants.d.ts +110 -0
  205. package/dist/plan-and-test-constants.d.ts.map +1 -0
  206. package/dist/plan-and-test-constants.js +132 -0
  207. package/dist/provider_factory.d.ts +99 -0
  208. package/dist/provider_factory.d.ts.map +1 -0
  209. package/dist/provider_factory.js +341 -0
  210. package/dist/provider_interface.d.ts +358 -0
  211. package/dist/provider_interface.d.ts.map +1 -0
  212. package/dist/provider_interface.js +28 -0
  213. package/dist/provider_utils.d.ts +39 -0
  214. package/dist/provider_utils.d.ts.map +1 -0
  215. package/dist/provider_utils.js +103 -0
  216. package/package.json +101 -0
  217. package/schemas/gap.schema.json +18 -0
  218. package/schemas/impact.schema.json +418 -0
  219. package/schemas/plan.schema.json +285 -0
  220. package/schemas/subsystem-risk-map.schema.json +62 -0
  221. package/schemas/traceability-input.schema.json +122 -0
@@ -0,0 +1,324 @@
1
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2
+ // See LICENSE.txt for license information.
3
+ import Anthropic from '@anthropic-ai/sdk';
4
+ import { LLMProviderError } from './provider_interface.js';
5
+ import { API_KEY_PATTERNS, sanitizeErrorMessage, withTimeout, validateAndSanitizeUrl } from './provider_utils.js';
6
+ import { BaseProvider } from './base_provider.js';
7
+ import { logger } from './logger.js';
8
+ /**
9
+ * Anthropic Provider - Claude AI models
10
+ *
11
+ * Features:
12
+ * - Highest quality AI (98% accuracy in testing)
13
+ * - Vision support (analyze screenshots, compare UI)
14
+ * - Fast response times (<1 second)
15
+ * - 200K token context window
16
+ * - Prompt caching (reduces costs by 90% on repeated prompts)
17
+ *
18
+ * Costs (Claude Sonnet 4.5):
19
+ * - Input: $3 per 1M tokens
20
+ * - Output: $15 per 1M tokens
21
+ * - Cached input: $0.30 per 1M tokens
22
+ * - Estimated: ~$30-80/month for autonomous testing
23
+ *
24
+ * Use cases:
25
+ * - Vision tasks (screenshot comparison)
26
+ * - Complex failure diagnosis
27
+ * - High-stakes production testing
28
+ * - When quality is paramount
29
+ *
30
+ * Models:
31
+ * - claude-sonnet-4-5-20250929 (recommended - best balance)
32
+ * - claude-opus-4-5-20251101 (highest quality, slower, more expensive)
33
+ * - claude-haiku-4-0-20250430 (fastest, cheapest, lower quality)
34
+ */
35
+ export class AnthropicProvider extends BaseProvider {
36
+ constructor(config) {
37
+ super();
38
+ this.name = 'anthropic';
39
+ this.capabilities = {
40
+ vision: true, // Full vision support
41
+ streaming: true,
42
+ maxTokens: 200000, // 200K context window
43
+ costPer1MInputTokens: 3, // $3 per 1M input tokens
44
+ costPer1MOutputTokens: 15, // $15 per 1M output tokens
45
+ supportsTools: true, // Function calling support
46
+ supportsPromptCaching: true, // Reduces costs by 90%
47
+ typicalResponseTimeMs: 800, // ~0.8 seconds
48
+ };
49
+ // SECURITY: Validate API key format
50
+ if (!API_KEY_PATTERNS.anthropic.test(config.apiKey)) {
51
+ throw new Error('Invalid API key format. Expected sk-ant-* format.');
52
+ }
53
+ // SECURITY: Validate and enforce HTTPS for remote connections
54
+ if (config.baseUrl) {
55
+ const validation = validateAndSanitizeUrl(config.baseUrl);
56
+ if (!validation.valid) {
57
+ throw new Error(`Invalid base URL: ${validation.warning}`);
58
+ }
59
+ if (validation.warning) {
60
+ logger.warn(`HTTPS required for remote URLs: ${validation.warning}`);
61
+ }
62
+ }
63
+ this.client = new Anthropic({
64
+ apiKey: config.apiKey,
65
+ baseURL: config.baseUrl,
66
+ });
67
+ this.model = config.model || 'claude-sonnet-4-5-20250929';
68
+ }
69
+ async generateText(prompt, options) {
70
+ const startTime = Date.now();
71
+ try {
72
+ // SECURITY: Validate prompt length to prevent resource exhaustion
73
+ if (prompt.length > 10 * 1024 * 1024) {
74
+ throw new Error('Prompt exceeds maximum size (10MB)');
75
+ }
76
+ const response = await withTimeout(this.client.messages.create({
77
+ model: this.model,
78
+ max_tokens: options?.maxTokens || 4000,
79
+ temperature: options?.temperature,
80
+ top_p: options?.topP,
81
+ stop_sequences: options?.stopSequences,
82
+ system: options?.systemPrompt,
83
+ messages: [
84
+ {
85
+ role: 'user',
86
+ content: prompt,
87
+ },
88
+ ],
89
+ }), options?.timeout, 'generateText');
90
+ const responseTime = Date.now() - startTime;
91
+ const text = this.extractTextFromResponse(response);
92
+ // SECURITY: Type-safe usage extraction
93
+ const usage = this.extractUsageFromResponse(response.usage);
94
+ const cost = this.calculateCost(usage, this.capabilities.costPer1MInputTokens, this.capabilities.costPer1MOutputTokens);
95
+ // Update stats
96
+ this.updateStats(usage, responseTime, cost);
97
+ return {
98
+ text,
99
+ usage,
100
+ cost,
101
+ metadata: {
102
+ model: this.model,
103
+ responseTimeMs: responseTime,
104
+ stopReason: response.stop_reason,
105
+ stopSequence: response.stop_sequence,
106
+ },
107
+ };
108
+ }
109
+ catch (error) {
110
+ this.stats.failedRequests++;
111
+ throw new LLMProviderError(sanitizeErrorMessage(error, 'generateText'), this.name, this.extractStatusCode(error), error);
112
+ }
113
+ }
114
+ async analyzeImage(images, prompt, options) {
115
+ const startTime = Date.now();
116
+ try {
117
+ // SECURITY: Validate image array size
118
+ if (images.length === 0 || images.length > 20) {
119
+ throw new Error('Image count must be between 1 and 20');
120
+ }
121
+ // SECURITY: Validate prompt length
122
+ if (prompt.length > 10 * 1024 * 1024) {
123
+ throw new Error('Prompt exceeds maximum size (10MB)');
124
+ }
125
+ // Build content array with text and images
126
+ const content = [];
127
+ // Add prompt text first
128
+ content.push({
129
+ type: 'text',
130
+ text: prompt,
131
+ });
132
+ // Add each image
133
+ for (const image of images) {
134
+ // Validate media type
135
+ const mediaType = (image.mimeType || image.mediaType || 'image/png');
136
+ if (!['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(mediaType)) {
137
+ throw new Error(`Unsupported image type: ${mediaType}`);
138
+ }
139
+ const data = image.data || image.base64 || '';
140
+ // SECURITY: Validate base64 data size (limit to 20MB per image)
141
+ if (data.length > 20 * 1024 * 1024) {
142
+ throw new Error('Image data exceeds maximum size (20MB)');
143
+ }
144
+ content.push({
145
+ type: 'image',
146
+ source: {
147
+ type: 'base64',
148
+ media_type: mediaType,
149
+ data: data,
150
+ },
151
+ });
152
+ // Add description if provided
153
+ if (image.description) {
154
+ content.push({
155
+ type: 'text',
156
+ text: `[Image: ${image.description}]`,
157
+ });
158
+ }
159
+ }
160
+ const response = await withTimeout(this.client.messages.create({
161
+ model: this.model,
162
+ max_tokens: options?.maxTokens || 4000,
163
+ temperature: options?.temperature,
164
+ top_p: options?.topP,
165
+ stop_sequences: options?.stopSequences,
166
+ system: options?.systemPrompt,
167
+ messages: [
168
+ {
169
+ role: 'user',
170
+ content,
171
+ },
172
+ ],
173
+ }), options?.timeout, 'analyzeImage');
174
+ const responseTime = Date.now() - startTime;
175
+ const text = this.extractTextFromResponse(response);
176
+ // SECURITY: Type-safe usage extraction
177
+ const usage = this.extractUsageFromResponse(response.usage);
178
+ const cost = this.calculateCost(usage, this.capabilities.costPer1MInputTokens, this.capabilities.costPer1MOutputTokens);
179
+ // Update stats
180
+ this.updateStats(usage, responseTime, cost);
181
+ return {
182
+ text,
183
+ usage,
184
+ cost,
185
+ metadata: {
186
+ model: this.model,
187
+ responseTimeMs: responseTime,
188
+ stopReason: response.stop_reason,
189
+ imageCount: images.length,
190
+ },
191
+ };
192
+ }
193
+ catch (error) {
194
+ this.stats.failedRequests++;
195
+ throw new LLMProviderError(sanitizeErrorMessage(error, 'analyzeImage'), this.name, this.extractStatusCode(error), error);
196
+ }
197
+ }
198
+ async *streamText(prompt, options) {
199
+ try {
200
+ // SECURITY: Validate prompt length
201
+ if (prompt.length > 10 * 1024 * 1024) {
202
+ throw new Error('Prompt exceeds maximum size (10MB)');
203
+ }
204
+ const stream = await withTimeout(this.client.messages.create({
205
+ model: this.model,
206
+ max_tokens: options?.maxTokens || 4000,
207
+ temperature: options?.temperature,
208
+ top_p: options?.topP,
209
+ stop_sequences: options?.stopSequences,
210
+ system: options?.systemPrompt,
211
+ messages: [
212
+ {
213
+ role: 'user',
214
+ content: prompt,
215
+ },
216
+ ],
217
+ stream: true,
218
+ }), options?.timeout, 'streamText');
219
+ for await (const event of stream) {
220
+ if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
221
+ yield event.delta.text;
222
+ }
223
+ }
224
+ // Note: Streaming doesn't provide detailed usage stats
225
+ // We increment request count but can't track exact tokens/cost
226
+ this.stats.requestCount++;
227
+ this.stats.lastUpdated = new Date();
228
+ }
229
+ catch (error) {
230
+ this.stats.failedRequests++;
231
+ throw new LLMProviderError(sanitizeErrorMessage(error, 'streamText'), this.name, this.extractStatusCode(error), error);
232
+ }
233
+ }
234
+ extractTextFromResponse(response) {
235
+ const textBlocks = response.content.filter((block) => block.type === 'text');
236
+ return textBlocks.map((block) => {
237
+ if (block.type === 'text') {
238
+ return block.text;
239
+ }
240
+ return '';
241
+ }).join('\n');
242
+ }
243
+ /**
244
+ * SECURITY: Type-safe usage extraction
245
+ * Avoids unsafe `as any` casts
246
+ */
247
+ extractUsageFromResponse(usage) {
248
+ return {
249
+ inputTokens: usage.input_tokens || 0,
250
+ outputTokens: usage.output_tokens || 0,
251
+ totalTokens: (usage.input_tokens || 0) + (usage.output_tokens || 0),
252
+ cachedTokens: usage.cache_read_input_tokens ?? undefined,
253
+ };
254
+ }
255
+ /**
256
+ * SECURITY: Extract status code safely
257
+ */
258
+ extractStatusCode(error) {
259
+ if (error && typeof error === 'object') {
260
+ const err = error;
261
+ const status = err.status;
262
+ if (typeof status === 'number') {
263
+ return status;
264
+ }
265
+ }
266
+ return undefined;
267
+ }
268
+ /**
269
+ * Check if API key is valid and service is accessible
270
+ */
271
+ async checkHealth() {
272
+ try {
273
+ // Try a minimal request to verify API key
274
+ await withTimeout(this.client.messages.create({
275
+ model: this.model,
276
+ max_tokens: 10,
277
+ messages: [
278
+ {
279
+ role: 'user',
280
+ content: 'Hi',
281
+ },
282
+ ],
283
+ }), 5000, 'health check');
284
+ return {
285
+ healthy: true,
286
+ message: `Anthropic API is accessible`,
287
+ };
288
+ }
289
+ catch (error) {
290
+ return {
291
+ healthy: false,
292
+ message: `Anthropic API error: ${sanitizeErrorMessage(error, 'health check')}`,
293
+ };
294
+ }
295
+ }
296
+ }
297
+ /**
298
+ * Helper to check Anthropic setup
299
+ */
300
+ export async function checkAnthropicSetup(apiKey) {
301
+ if (!apiKey) {
302
+ return {
303
+ valid: false,
304
+ message: 'No API key provided',
305
+ estimatedMonthlyCost: 'N/A',
306
+ };
307
+ }
308
+ try {
309
+ const provider = new AnthropicProvider({ apiKey });
310
+ const health = await provider.checkHealth();
311
+ return {
312
+ valid: health.healthy,
313
+ message: health.message,
314
+ estimatedMonthlyCost: '$30-80 for autonomous testing (24 cycles/day)',
315
+ };
316
+ }
317
+ catch (error) {
318
+ return {
319
+ valid: false,
320
+ message: `Setup check failed: ${sanitizeErrorMessage(error, 'setup check')}`,
321
+ estimatedMonthlyCost: 'N/A',
322
+ };
323
+ }
324
+ }
@@ -0,0 +1,105 @@
1
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2
+ // See LICENSE.txt for license information.
3
+ import { existsSync, readFileSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { resolveConfig } from './agent/config.js';
6
+ import { runGap, runImpact } from './agent/runner.js';
7
+ import { attachDeveloperActions, buildPlanFromImpactReport, renderCiSummaryMarkdown, writeCiSummary, writePlanReport, } from './agent/plan.js';
8
+ import { applyOperationalInsights } from './agent/operational_insights.js';
9
+ import { finalizeGeneratedTests } from './agent/handoff.js';
10
+ import { ingestTraceabilityInput, } from './agent/traceability_ingest.js';
11
+ import { captureTraceabilityInput, } from './agent/traceability_capture.js';
12
+ function readReportJson(reportPath) {
13
+ if (!existsSync(reportPath)) {
14
+ throw new Error(`Expected report not found: ${reportPath}`);
15
+ }
16
+ const raw = readFileSync(reportPath, 'utf-8');
17
+ return JSON.parse(raw);
18
+ }
19
+ function resolveAgent(options, mode) {
20
+ const cwd = options.cwd || process.cwd();
21
+ const { config } = resolveConfig(cwd, options.configPath, {
22
+ ...options,
23
+ mode,
24
+ });
25
+ if (options.allowFallback) {
26
+ config.impact.allowFallback = true;
27
+ }
28
+ return config;
29
+ }
30
+ function reportPathFor(configPath, mode) {
31
+ return join(configPath, '.e2e-ai-agents', mode === 'impact' ? 'impact.json' : 'gap.json');
32
+ }
33
+ export async function analyzeImpact(options = {}) {
34
+ const config = resolveAgent(options, 'impact');
35
+ await runImpact(config, { apply: options.apply ?? false });
36
+ const reportRoot = config.testsRoot || config.path;
37
+ const reportPath = reportPathFor(reportRoot, 'impact');
38
+ const report = readReportJson(reportPath);
39
+ return { report, reportPath };
40
+ }
41
+ export async function findGaps(options = {}) {
42
+ const config = resolveAgent(options, 'gap');
43
+ await runGap(config, { apply: options.apply ?? false });
44
+ const reportRoot = config.testsRoot || config.path;
45
+ const reportPath = reportPathFor(reportRoot, 'gap');
46
+ const report = readReportJson(reportPath);
47
+ return { report, reportPath };
48
+ }
49
+ export async function recommendTests(options = {}) {
50
+ const config = resolveAgent(options, 'impact');
51
+ await runImpact(config, { apply: options.apply ?? false });
52
+ const reportRoot = config.testsRoot || config.path;
53
+ const impactPath = reportPathFor(reportRoot, 'impact');
54
+ const report = readReportJson(impactPath);
55
+ const basePlan = buildPlanFromImpactReport(report, config.policy);
56
+ const withActions = attachDeveloperActions(basePlan, {
57
+ appPath: config.path,
58
+ testsRoot: reportRoot,
59
+ sinceRef: config.git.since,
60
+ });
61
+ const plan = applyOperationalInsights(withActions, reportRoot);
62
+ const planPath = writePlanReport(reportRoot, plan);
63
+ const ciSummaryMarkdown = renderCiSummaryMarkdown(plan);
64
+ const ciSummaryPath = writeCiSummary(reportRoot, ciSummaryMarkdown);
65
+ return {
66
+ report,
67
+ reportPath: impactPath,
68
+ plan,
69
+ planPath,
70
+ ciSummaryMarkdown,
71
+ ciSummaryPath,
72
+ };
73
+ }
74
+ export function handoffGeneratedTests(options) {
75
+ return finalizeGeneratedTests(options);
76
+ }
77
+ export function ingestTraceability(options) {
78
+ const cwd = options.cwd || process.cwd();
79
+ const { config } = resolveConfig(cwd, options.configPath, {
80
+ path: options.path,
81
+ testsRoot: options.testsRoot,
82
+ mode: 'impact',
83
+ });
84
+ const reportRoot = config.testsRoot || config.path;
85
+ return ingestTraceabilityInput(reportRoot, config.impact.traceability, options.payload, options.options);
86
+ }
87
+ export function captureTraceability(options) {
88
+ const cwd = options.cwd || process.cwd();
89
+ const { config } = resolveConfig(cwd, options.configPath, {
90
+ path: options.path,
91
+ testsRoot: options.testsRoot,
92
+ mode: 'impact',
93
+ });
94
+ const reportRoot = config.testsRoot || config.path;
95
+ const captureOptions = {
96
+ appPath: config.path,
97
+ testsRoot: reportRoot,
98
+ reportPath: options.reportPath,
99
+ sinceRef: options.sinceRef || config.git.since,
100
+ outputPath: options.outputPath,
101
+ coverageMapPath: options.coverageMapPath,
102
+ changedFilesPath: options.changedFilesPath,
103
+ };
104
+ return captureTraceabilityInput(captureOptions);
105
+ }
@@ -0,0 +1,77 @@
1
+ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2
+ // See LICENSE.txt for license information.
3
+ /**
4
+ * Abstract base class for all LLM providers
5
+ * Eliminates 240+ lines of duplicate stats management code
6
+ * Provides common functionality for token tracking, cost calculation, and stats management
7
+ */
8
+ export class BaseProvider {
9
+ constructor() {
10
+ this.initializeStats();
11
+ }
12
+ /**
13
+ * Initialize stats object with default values
14
+ */
15
+ initializeStats() {
16
+ this.stats = {
17
+ requestCount: 0,
18
+ totalInputTokens: 0,
19
+ totalOutputTokens: 0,
20
+ totalTokens: 0,
21
+ totalCost: 0,
22
+ averageResponseTimeMs: 0,
23
+ failedRequests: 0,
24
+ startTime: new Date(),
25
+ lastUpdated: new Date(),
26
+ };
27
+ }
28
+ /**
29
+ * Update stats with new usage data
30
+ * Maintains rolling average for response time
31
+ */
32
+ updateStats(usage, responseTime, cost) {
33
+ this.stats.requestCount++;
34
+ this.stats.totalInputTokens += usage.inputTokens;
35
+ this.stats.totalOutputTokens += usage.outputTokens;
36
+ this.stats.totalTokens += usage.totalTokens;
37
+ this.stats.totalCost += cost;
38
+ // Update rolling average response time
39
+ const totalRequests = this.stats.requestCount;
40
+ this.stats.averageResponseTimeMs =
41
+ (this.stats.averageResponseTimeMs * (totalRequests - 1) + responseTime) / totalRequests;
42
+ this.stats.lastUpdated = new Date();
43
+ }
44
+ /**
45
+ * Get a copy of current usage stats
46
+ */
47
+ getUsageStats() {
48
+ return { ...this.stats };
49
+ }
50
+ /**
51
+ * Reset all usage stats to initial state
52
+ */
53
+ resetUsageStats() {
54
+ this.initializeStats();
55
+ }
56
+ /**
57
+ * Calculate cost for token usage, accounting for prompt caching discounts
58
+ * Cached tokens cost 90% less than regular tokens
59
+ */
60
+ calculateCost(usage, costPer1MInputTokens, costPer1MOutputTokens) {
61
+ // Calculate input token cost
62
+ let inputCost = 0;
63
+ // Cached tokens cost 90% less
64
+ if (usage.cachedTokens) {
65
+ const cachedCost = (usage.cachedTokens / 1000000) * (costPer1MInputTokens * 0.1);
66
+ const uncachedInputTokens = usage.inputTokens - usage.cachedTokens;
67
+ const uncachedCost = (uncachedInputTokens / 1000000) * costPer1MInputTokens;
68
+ inputCost = cachedCost + uncachedCost;
69
+ }
70
+ else {
71
+ inputCost = (usage.inputTokens / 1000000) * costPer1MInputTokens;
72
+ }
73
+ // Calculate output token cost
74
+ const outputCost = (usage.outputTokens / 1000000) * costPer1MOutputTokens;
75
+ return inputCost + outputCost;
76
+ }
77
+ }