@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.
- package/LICENSE +168 -0
- package/README.md +620 -0
- package/dist/agent/analysis.d.ts +62 -0
- package/dist/agent/analysis.d.ts.map +1 -0
- package/dist/agent/analysis.js +292 -0
- package/dist/agent/blast_radius.d.ts +4 -0
- package/dist/agent/blast_radius.d.ts.map +1 -0
- package/dist/agent/blast_radius.js +37 -0
- package/dist/agent/cache_utils.d.ts +38 -0
- package/dist/agent/cache_utils.d.ts.map +1 -0
- package/dist/agent/cache_utils.js +67 -0
- package/dist/agent/config.d.ts +148 -0
- package/dist/agent/config.d.ts.map +1 -0
- package/dist/agent/config.js +640 -0
- package/dist/agent/dependency_graph.d.ts +14 -0
- package/dist/agent/dependency_graph.d.ts.map +1 -0
- package/dist/agent/dependency_graph.js +227 -0
- package/dist/agent/feedback.d.ts +55 -0
- package/dist/agent/feedback.d.ts.map +1 -0
- package/dist/agent/feedback.js +257 -0
- package/dist/agent/flags.d.ts +23 -0
- package/dist/agent/flags.d.ts.map +1 -0
- package/dist/agent/flags.js +171 -0
- package/dist/agent/flow_catalog.d.ts +25 -0
- package/dist/agent/flow_catalog.d.ts.map +1 -0
- package/dist/agent/flow_catalog.js +106 -0
- package/dist/agent/flow_mapping.d.ts +10 -0
- package/dist/agent/flow_mapping.d.ts.map +1 -0
- package/dist/agent/flow_mapping.js +84 -0
- package/dist/agent/framework.d.ts +13 -0
- package/dist/agent/framework.d.ts.map +1 -0
- package/dist/agent/framework.js +149 -0
- package/dist/agent/gap_suggestions.d.ts +14 -0
- package/dist/agent/gap_suggestions.d.ts.map +1 -0
- package/dist/agent/gap_suggestions.js +101 -0
- package/dist/agent/generator.d.ts +10 -0
- package/dist/agent/generator.d.ts.map +1 -0
- package/dist/agent/generator.js +115 -0
- package/dist/agent/git.d.ts +11 -0
- package/dist/agent/git.d.ts.map +1 -0
- package/dist/agent/git.js +90 -0
- package/dist/agent/handoff.d.ts +22 -0
- package/dist/agent/handoff.d.ts.map +1 -0
- package/dist/agent/handoff.js +180 -0
- package/dist/agent/impact-analyzer.d.ts +114 -0
- package/dist/agent/impact-analyzer.d.ts.map +1 -0
- package/dist/agent/impact-analyzer.js +557 -0
- package/dist/agent/index.d.ts +21 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +38 -0
- package/dist/agent/model-router.d.ts +57 -0
- package/dist/agent/model-router.d.ts.map +1 -0
- package/dist/agent/model-router.js +154 -0
- package/dist/agent/operational_insights.d.ts +41 -0
- package/dist/agent/operational_insights.d.ts.map +1 -0
- package/dist/agent/operational_insights.js +126 -0
- package/dist/agent/pipeline.d.ts +23 -0
- package/dist/agent/pipeline.d.ts.map +1 -0
- package/dist/agent/pipeline.js +609 -0
- package/dist/agent/plan.d.ts +91 -0
- package/dist/agent/plan.d.ts.map +1 -0
- package/dist/agent/plan.js +331 -0
- package/dist/agent/playwright_report.d.ts +8 -0
- package/dist/agent/playwright_report.d.ts.map +1 -0
- package/dist/agent/playwright_report.js +126 -0
- package/dist/agent/report-generator.d.ts +24 -0
- package/dist/agent/report-generator.d.ts.map +1 -0
- package/dist/agent/report-generator.js +250 -0
- package/dist/agent/report.d.ts +81 -0
- package/dist/agent/report.d.ts.map +1 -0
- package/dist/agent/report.js +147 -0
- package/dist/agent/runner.d.ts +7 -0
- package/dist/agent/runner.d.ts.map +1 -0
- package/dist/agent/runner.js +576 -0
- package/dist/agent/selectors.d.ts +10 -0
- package/dist/agent/selectors.d.ts.map +1 -0
- package/dist/agent/selectors.js +75 -0
- package/dist/agent/spec-bridge.d.ts +101 -0
- package/dist/agent/spec-bridge.d.ts.map +1 -0
- package/dist/agent/spec-bridge.js +273 -0
- package/dist/agent/spec-builder.d.ts +102 -0
- package/dist/agent/spec-builder.d.ts.map +1 -0
- package/dist/agent/spec-builder.js +273 -0
- package/dist/agent/subsystem_risk.d.ts +23 -0
- package/dist/agent/subsystem_risk.d.ts.map +1 -0
- package/dist/agent/subsystem_risk.js +207 -0
- package/dist/agent/telemetry.d.ts +84 -0
- package/dist/agent/telemetry.d.ts.map +1 -0
- package/dist/agent/telemetry.js +220 -0
- package/dist/agent/test_path.d.ts +2 -0
- package/dist/agent/test_path.d.ts.map +1 -0
- package/dist/agent/test_path.js +23 -0
- package/dist/agent/tests.d.ts +18 -0
- package/dist/agent/tests.d.ts.map +1 -0
- package/dist/agent/tests.js +106 -0
- package/dist/agent/traceability.d.ts +22 -0
- package/dist/agent/traceability.d.ts.map +1 -0
- package/dist/agent/traceability.js +183 -0
- package/dist/agent/traceability_capture.d.ts +18 -0
- package/dist/agent/traceability_capture.d.ts.map +1 -0
- package/dist/agent/traceability_capture.js +313 -0
- package/dist/agent/traceability_ingest.d.ts +21 -0
- package/dist/agent/traceability_ingest.d.ts.map +1 -0
- package/dist/agent/traceability_ingest.js +237 -0
- package/dist/agent/utils.d.ts +13 -0
- package/dist/agent/utils.d.ts.map +1 -0
- package/dist/agent/utils.js +152 -0
- package/dist/agent/validators/selector-validator.d.ts +74 -0
- package/dist/agent/validators/selector-validator.d.ts.map +1 -0
- package/dist/agent/validators/selector-validator.js +165 -0
- package/dist/anthropic_provider.d.ts +65 -0
- package/dist/anthropic_provider.d.ts.map +1 -0
- package/dist/anthropic_provider.js +332 -0
- package/dist/api.d.ts +48 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +113 -0
- package/dist/base_provider.d.ts +53 -0
- package/dist/base_provider.d.ts.map +1 -0
- package/dist/base_provider.js +81 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +843 -0
- package/dist/custom_provider.d.ts +20 -0
- package/dist/custom_provider.d.ts.map +1 -0
- package/dist/custom_provider.js +276 -0
- package/dist/e2e-test-gen/index.d.ts +51 -0
- package/dist/e2e-test-gen/index.d.ts.map +1 -0
- package/dist/e2e-test-gen/index.js +57 -0
- package/dist/e2e-test-gen/spec_parser.d.ts +142 -0
- package/dist/e2e-test-gen/spec_parser.d.ts.map +1 -0
- package/dist/e2e-test-gen/spec_parser.js +786 -0
- package/dist/e2e-test-gen/types.d.ts +185 -0
- package/dist/e2e-test-gen/types.d.ts.map +1 -0
- package/dist/e2e-test-gen/types.js +4 -0
- package/dist/esm/agent/analysis.js +287 -0
- package/dist/esm/agent/blast_radius.js +34 -0
- package/dist/esm/agent/cache_utils.js +63 -0
- package/dist/esm/agent/config.js +637 -0
- package/dist/esm/agent/dependency_graph.js +224 -0
- package/dist/esm/agent/feedback.js +253 -0
- package/dist/esm/agent/flags.js +160 -0
- package/dist/esm/agent/flow_catalog.js +103 -0
- package/dist/esm/agent/flow_mapping.js +81 -0
- package/dist/esm/agent/framework.js +145 -0
- package/dist/esm/agent/gap_suggestions.js +98 -0
- package/dist/esm/agent/generator.js +112 -0
- package/dist/esm/agent/git.js +87 -0
- package/dist/esm/agent/handoff.js +177 -0
- package/dist/esm/agent/impact-analyzer.js +548 -0
- package/dist/esm/agent/index.js +22 -0
- package/dist/esm/agent/model-router.js +150 -0
- package/dist/esm/agent/operational_insights.js +123 -0
- package/dist/esm/agent/pipeline.js +605 -0
- package/dist/esm/agent/plan.js +324 -0
- package/dist/esm/agent/playwright_report.js +123 -0
- package/dist/esm/agent/report-generator.js +247 -0
- package/dist/esm/agent/report.js +144 -0
- package/dist/esm/agent/runner.js +572 -0
- package/dist/esm/agent/selectors.js +71 -0
- package/dist/esm/agent/spec-bridge.js +267 -0
- package/dist/esm/agent/spec-builder.js +267 -0
- package/dist/esm/agent/subsystem_risk.js +204 -0
- package/dist/esm/agent/telemetry.js +216 -0
- package/dist/esm/agent/test_path.js +20 -0
- package/dist/esm/agent/tests.js +101 -0
- package/dist/esm/agent/traceability.js +180 -0
- package/dist/esm/agent/traceability_capture.js +310 -0
- package/dist/esm/agent/traceability_ingest.js +234 -0
- package/dist/esm/agent/utils.js +138 -0
- package/dist/esm/agent/validators/selector-validator.js +160 -0
- package/dist/esm/anthropic_provider.js +324 -0
- package/dist/esm/api.js +105 -0
- package/dist/esm/base_provider.js +77 -0
- package/dist/esm/cli.js +841 -0
- package/dist/esm/custom_provider.js +272 -0
- package/dist/esm/e2e-test-gen/index.js +50 -0
- package/dist/esm/e2e-test-gen/spec_parser.js +782 -0
- package/dist/esm/e2e-test-gen/types.js +3 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/logger.js +89 -0
- package/dist/esm/mcp-server.js +465 -0
- package/dist/esm/ollama_provider.js +300 -0
- package/dist/esm/openai_provider.js +242 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/plan-and-test-constants.js +126 -0
- package/dist/esm/provider_factory.js +336 -0
- package/dist/esm/provider_interface.js +23 -0
- package/dist/esm/provider_utils.js +96 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/logger.d.ts +23 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +93 -0
- package/dist/mcp-server.d.ts +35 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +469 -0
- package/dist/ollama_provider.d.ts +65 -0
- package/dist/ollama_provider.d.ts.map +1 -0
- package/dist/ollama_provider.js +308 -0
- package/dist/openai_provider.d.ts +23 -0
- package/dist/openai_provider.d.ts.map +1 -0
- package/dist/openai_provider.js +250 -0
- package/dist/plan-and-test-constants.d.ts +110 -0
- package/dist/plan-and-test-constants.d.ts.map +1 -0
- package/dist/plan-and-test-constants.js +132 -0
- package/dist/provider_factory.d.ts +99 -0
- package/dist/provider_factory.d.ts.map +1 -0
- package/dist/provider_factory.js +341 -0
- package/dist/provider_interface.d.ts +358 -0
- package/dist/provider_interface.d.ts.map +1 -0
- package/dist/provider_interface.js +28 -0
- package/dist/provider_utils.d.ts +39 -0
- package/dist/provider_utils.d.ts.map +1 -0
- package/dist/provider_utils.js +103 -0
- package/package.json +101 -0
- package/schemas/gap.schema.json +18 -0
- package/schemas/impact.schema.json +418 -0
- package/schemas/plan.schema.json +285 -0
- package/schemas/subsystem-risk-map.schema.json +62 -0
- 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
|
+
}
|
package/dist/esm/api.js
ADDED
|
@@ -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
|
+
}
|