@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,20 @@
|
|
|
1
|
+
import type { CustomConfig, GenerateOptions, ImageInput, LLMResponse, ProviderCapabilities } from './provider_interface.js';
|
|
2
|
+
import { BaseProvider } from './base_provider.js';
|
|
3
|
+
export declare class CustomProvider extends BaseProvider {
|
|
4
|
+
name: string;
|
|
5
|
+
private config;
|
|
6
|
+
capabilities: ProviderCapabilities;
|
|
7
|
+
constructor(config: CustomConfig);
|
|
8
|
+
generateText(prompt: string, options?: GenerateOptions): Promise<LLMResponse>;
|
|
9
|
+
analyzeImage(images: ImageInput[], prompt: string, options?: GenerateOptions): Promise<LLMResponse>;
|
|
10
|
+
private dispatchRequest;
|
|
11
|
+
private requestOpenAI;
|
|
12
|
+
private requestAnthropic;
|
|
13
|
+
private requestCustom;
|
|
14
|
+
streamText(): AsyncGenerator<string, void, unknown>;
|
|
15
|
+
checkHealth(): Promise<{
|
|
16
|
+
healthy: boolean;
|
|
17
|
+
message: string;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=custom_provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom_provider.d.ts","sourceRoot":"","sources":["../src/custom_provider.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACR,YAAY,EACZ,eAAe,EACf,UAAU,EACV,WAAW,EACX,oBAAoB,EACvB,MAAM,yBAAyB,CAAC;AAGjC,OAAO,EAAC,YAAY,EAAC,MAAM,oBAAoB,CAAC;AA6DhD,qBAAa,cAAe,SAAQ,YAAY;IAC5C,IAAI,SAAY;IAChB,OAAO,CAAC,MAAM,CAAe;IAE7B,YAAY,EAAE,oBAAoB,CAAC;gBAEvB,MAAM,EAAE,YAAY;IAyB1B,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IAkC7E,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;YAyC3F,eAAe;YAiBf,aAAa;YA8Eb,gBAAgB;YA6EhB,aAAa;IAqBpB,UAAU,IAAI,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC;IAKpD,WAAW,IAAI,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC,CAAC;CAOpE"}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
3
|
+
// See LICENSE.txt for license information.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.CustomProvider = void 0;
|
|
6
|
+
const provider_interface_js_1 = require("./provider_interface.js");
|
|
7
|
+
const provider_utils_js_1 = require("./provider_utils.js");
|
|
8
|
+
const base_provider_js_1 = require("./base_provider.js");
|
|
9
|
+
const logger_js_1 = require("./logger.js");
|
|
10
|
+
function normalizeUrl(baseUrl, pathSuffix) {
|
|
11
|
+
const trimmed = baseUrl.replace(/\/+$/, '');
|
|
12
|
+
if (trimmed.endsWith(pathSuffix)) {
|
|
13
|
+
return trimmed;
|
|
14
|
+
}
|
|
15
|
+
return `${trimmed}${pathSuffix}`;
|
|
16
|
+
}
|
|
17
|
+
async function postJson(url, headers, body, timeoutMs, context) {
|
|
18
|
+
const controller = new AbortController();
|
|
19
|
+
const timer = timeoutMs ? setTimeout(() => controller.abort(), timeoutMs) : undefined;
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(url, {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: {
|
|
24
|
+
'content-type': 'application/json',
|
|
25
|
+
...headers,
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify(body),
|
|
28
|
+
signal: controller.signal,
|
|
29
|
+
});
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`HTTP ${response.status}`);
|
|
32
|
+
}
|
|
33
|
+
return (await response.json());
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (error instanceof Error && error.name === 'AbortError') {
|
|
37
|
+
throw new Error(`Request timeout (${context})`);
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
if (timer) {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
class CustomProvider extends base_provider_js_1.BaseProvider {
|
|
48
|
+
constructor(config) {
|
|
49
|
+
super();
|
|
50
|
+
this.name = 'custom';
|
|
51
|
+
const validation = (0, provider_utils_js_1.validateAndSanitizeUrl)(config.baseUrl);
|
|
52
|
+
if (!validation.valid) {
|
|
53
|
+
throw new Error(`Invalid base URL: ${validation.warning}`);
|
|
54
|
+
}
|
|
55
|
+
if (validation.warning) {
|
|
56
|
+
logger_js_1.logger.warn(`HTTPS required for remote URLs: ${validation.warning}`);
|
|
57
|
+
}
|
|
58
|
+
this.config = config;
|
|
59
|
+
this.capabilities = {
|
|
60
|
+
vision: config.requestFormat !== 'custom',
|
|
61
|
+
streaming: false,
|
|
62
|
+
maxTokens: 0,
|
|
63
|
+
costPer1MInputTokens: 0,
|
|
64
|
+
costPer1MOutputTokens: 0,
|
|
65
|
+
supportsTools: false,
|
|
66
|
+
supportsPromptCaching: false,
|
|
67
|
+
typicalResponseTimeMs: 0,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
async generateText(prompt, options) {
|
|
71
|
+
const startTime = Date.now();
|
|
72
|
+
try {
|
|
73
|
+
if (prompt.length > 10 * 1024 * 1024) {
|
|
74
|
+
throw new Error('Prompt exceeds maximum size (10MB)');
|
|
75
|
+
}
|
|
76
|
+
const response = await this.dispatchRequest(prompt, options);
|
|
77
|
+
const responseTime = Date.now() - startTime;
|
|
78
|
+
const text = response.text;
|
|
79
|
+
const usage = response.usage;
|
|
80
|
+
const cost = response.cost;
|
|
81
|
+
this.updateStats(usage, responseTime, cost);
|
|
82
|
+
return {
|
|
83
|
+
text,
|
|
84
|
+
usage,
|
|
85
|
+
cost,
|
|
86
|
+
metadata: response.metadata,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
this.stats.failedRequests++;
|
|
91
|
+
throw new provider_interface_js_1.LLMProviderError((0, provider_utils_js_1.sanitizeErrorMessage)(error, 'generateText'), this.name, undefined, error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async analyzeImage(images, prompt, options) {
|
|
95
|
+
if (this.config.requestFormat === 'custom') {
|
|
96
|
+
throw new provider_interface_js_1.UnsupportedCapabilityError(this.name, 'vision');
|
|
97
|
+
}
|
|
98
|
+
const startTime = Date.now();
|
|
99
|
+
try {
|
|
100
|
+
if (images.length === 0 || images.length > 20) {
|
|
101
|
+
throw new Error('Image count must be between 1 and 20');
|
|
102
|
+
}
|
|
103
|
+
if (prompt.length > 10 * 1024 * 1024) {
|
|
104
|
+
throw new Error('Prompt exceeds maximum size (10MB)');
|
|
105
|
+
}
|
|
106
|
+
const response = await this.dispatchRequest(prompt, options, images);
|
|
107
|
+
const responseTime = Date.now() - startTime;
|
|
108
|
+
const text = response.text;
|
|
109
|
+
const usage = response.usage;
|
|
110
|
+
const cost = response.cost;
|
|
111
|
+
this.updateStats(usage, responseTime, cost);
|
|
112
|
+
return {
|
|
113
|
+
text,
|
|
114
|
+
usage,
|
|
115
|
+
cost,
|
|
116
|
+
metadata: response.metadata,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
this.stats.failedRequests++;
|
|
121
|
+
throw new provider_interface_js_1.LLMProviderError((0, provider_utils_js_1.sanitizeErrorMessage)(error, 'analyzeImage'), this.name, undefined, error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async dispatchRequest(prompt, options, images) {
|
|
125
|
+
switch (this.config.requestFormat) {
|
|
126
|
+
case 'openai':
|
|
127
|
+
return this.requestOpenAI(prompt, options, images);
|
|
128
|
+
case 'anthropic':
|
|
129
|
+
return this.requestAnthropic(prompt, options, images);
|
|
130
|
+
case 'custom':
|
|
131
|
+
return this.requestCustom(prompt, options);
|
|
132
|
+
default:
|
|
133
|
+
throw new Error(`Unsupported request format: ${this.config.requestFormat}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async requestOpenAI(prompt, options, images) {
|
|
137
|
+
const url = normalizeUrl(this.config.baseUrl, '/chat/completions');
|
|
138
|
+
const messages = [];
|
|
139
|
+
if (options?.systemPrompt) {
|
|
140
|
+
messages.push({ role: 'system', content: options.systemPrompt });
|
|
141
|
+
}
|
|
142
|
+
if (images && images.length > 0) {
|
|
143
|
+
const content = [
|
|
144
|
+
{ type: 'text', text: prompt },
|
|
145
|
+
];
|
|
146
|
+
for (const image of images) {
|
|
147
|
+
const mediaType = (image.mimeType || image.mediaType || 'image/png');
|
|
148
|
+
if (!['image/png', 'image/jpeg', 'image/webp'].includes(mediaType)) {
|
|
149
|
+
throw new Error(`Unsupported image type: ${mediaType}`);
|
|
150
|
+
}
|
|
151
|
+
const data = image.data || image.base64 || '';
|
|
152
|
+
if (data.length > 20 * 1024 * 1024) {
|
|
153
|
+
throw new Error('Image data exceeds maximum size (20MB)');
|
|
154
|
+
}
|
|
155
|
+
content.push({
|
|
156
|
+
type: 'image_url',
|
|
157
|
+
image_url: { url: `data:${mediaType};base64,${data}` },
|
|
158
|
+
});
|
|
159
|
+
if (image.description) {
|
|
160
|
+
content.push({ type: 'text', text: `[Image: ${image.description}]` });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
messages.push({ role: 'user', content });
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
messages.push({ role: 'user', content: prompt });
|
|
167
|
+
}
|
|
168
|
+
const body = {
|
|
169
|
+
model: this.config.model,
|
|
170
|
+
messages,
|
|
171
|
+
max_tokens: options?.maxTokens,
|
|
172
|
+
temperature: options?.temperature,
|
|
173
|
+
top_p: options?.topP,
|
|
174
|
+
stop: options?.stopSequences,
|
|
175
|
+
};
|
|
176
|
+
const response = await postJson(url, this.config.auth, body, options?.timeout, 'openai');
|
|
177
|
+
const text = response.choices?.[0]?.message?.content || '';
|
|
178
|
+
const usage = {
|
|
179
|
+
inputTokens: response.usage?.prompt_tokens || 0,
|
|
180
|
+
outputTokens: response.usage?.completion_tokens || 0,
|
|
181
|
+
totalTokens: response.usage?.total_tokens || 0,
|
|
182
|
+
};
|
|
183
|
+
return {
|
|
184
|
+
text,
|
|
185
|
+
usage,
|
|
186
|
+
cost: 0,
|
|
187
|
+
metadata: {
|
|
188
|
+
finishReason: response.choices?.[0]?.finish_reason,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
async requestAnthropic(prompt, options, images) {
|
|
193
|
+
const url = normalizeUrl(this.config.baseUrl, '/messages');
|
|
194
|
+
const content = [
|
|
195
|
+
{ type: 'text', text: prompt },
|
|
196
|
+
];
|
|
197
|
+
if (images && images.length > 0) {
|
|
198
|
+
for (const image of images) {
|
|
199
|
+
const mediaType = (image.mimeType || image.mediaType || 'image/png');
|
|
200
|
+
if (!['image/png', 'image/jpeg', 'image/webp', 'image/gif'].includes(mediaType)) {
|
|
201
|
+
throw new Error(`Unsupported image type: ${mediaType}`);
|
|
202
|
+
}
|
|
203
|
+
const data = image.data || image.base64 || '';
|
|
204
|
+
if (data.length > 20 * 1024 * 1024) {
|
|
205
|
+
throw new Error('Image data exceeds maximum size (20MB)');
|
|
206
|
+
}
|
|
207
|
+
content.push({
|
|
208
|
+
type: 'image',
|
|
209
|
+
source: {
|
|
210
|
+
type: 'base64',
|
|
211
|
+
media_type: mediaType,
|
|
212
|
+
data,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
if (image.description) {
|
|
216
|
+
content.push({ type: 'text', text: `[Image: ${image.description}]` });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const body = {
|
|
221
|
+
model: this.config.model,
|
|
222
|
+
max_tokens: options?.maxTokens || 4000,
|
|
223
|
+
temperature: options?.temperature,
|
|
224
|
+
top_p: options?.topP,
|
|
225
|
+
stop_sequences: options?.stopSequences,
|
|
226
|
+
system: options?.systemPrompt,
|
|
227
|
+
messages: [
|
|
228
|
+
{
|
|
229
|
+
role: 'user',
|
|
230
|
+
content,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
const response = await postJson(url, this.config.auth, body, options?.timeout, 'anthropic');
|
|
235
|
+
const text = (response.content || []).map((block) => block.text).join('\n');
|
|
236
|
+
const usage = {
|
|
237
|
+
inputTokens: response.usage?.input_tokens || 0,
|
|
238
|
+
outputTokens: response.usage?.output_tokens || 0,
|
|
239
|
+
totalTokens: (response.usage?.input_tokens || 0) + (response.usage?.output_tokens || 0),
|
|
240
|
+
};
|
|
241
|
+
return {
|
|
242
|
+
text,
|
|
243
|
+
usage,
|
|
244
|
+
cost: 0,
|
|
245
|
+
metadata: {
|
|
246
|
+
stopReason: response.stop_reason,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
async requestCustom(prompt, options) {
|
|
251
|
+
if (!this.config.transformRequest || !this.config.transformResponse) {
|
|
252
|
+
throw new Error('Custom providers require transformRequest and transformResponse');
|
|
253
|
+
}
|
|
254
|
+
const body = this.config.transformRequest(prompt, options);
|
|
255
|
+
const response = await postJson(this.config.baseUrl, this.config.auth, body, options?.timeout, 'custom');
|
|
256
|
+
const transformed = this.config.transformResponse(response);
|
|
257
|
+
return {
|
|
258
|
+
text: transformed.text,
|
|
259
|
+
usage: transformed.usage,
|
|
260
|
+
cost: transformed.cost,
|
|
261
|
+
metadata: transformed.metadata,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
// CustomProvider doesn't support streaming
|
|
265
|
+
async *streamText() {
|
|
266
|
+
throw new Error('Streaming not supported for custom providers');
|
|
267
|
+
}
|
|
268
|
+
// CustomProvider doesn't have built-in health checks
|
|
269
|
+
async checkHealth() {
|
|
270
|
+
return {
|
|
271
|
+
healthy: true,
|
|
272
|
+
message: 'Custom provider configured',
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
exports.CustomProvider = CustomProvider;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autonomous E2E Testing System
|
|
3
|
+
*
|
|
4
|
+
* A specification-driven testing system that bridges PDF/Markdown specs
|
|
5
|
+
* with Playwright's native agents for test planning, generation, and healing.
|
|
6
|
+
*
|
|
7
|
+
* Quick Start:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import {SpecBridge, createAnthropicBridge} from '@mattermost/playwright-lib/autonomous';
|
|
10
|
+
*
|
|
11
|
+
* // Convert a specification to Playwright-compatible markdown
|
|
12
|
+
* const bridge = createAnthropicBridge(process.env.ANTHROPIC_API_KEY);
|
|
13
|
+
* const result = await bridge.convertToPlaywrightSpecs('spec.pdf', 'specs/');
|
|
14
|
+
*
|
|
15
|
+
* // Then use Playwright agents:
|
|
16
|
+
* // @planner explore http://localhost:8065
|
|
17
|
+
* // @generator create tests from specs/
|
|
18
|
+
* // @healer fix failing tests
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Architecture:
|
|
22
|
+
* - SpecificationParser: Parses PDF/MD/JSON specs into structured format
|
|
23
|
+
* - SpecBridge: Converts specs to Playwright Agent-compatible markdown
|
|
24
|
+
* - LLM Providers: Pluggable AI providers (Anthropic, Ollama, OpenAI)
|
|
25
|
+
*
|
|
26
|
+
* The heavy lifting (test generation, execution, healing) is delegated to
|
|
27
|
+
* Playwright's built-in agents which are production-ready and maintained
|
|
28
|
+
* by the Playwright team.
|
|
29
|
+
*/
|
|
30
|
+
export { SpecificationParser } from './spec_parser.js';
|
|
31
|
+
export type { SpecSummary, SpecificationCache } from './spec_parser.js';
|
|
32
|
+
export { LLMProviderFactory } from '../provider_factory.js';
|
|
33
|
+
export { OllamaProvider } from '../ollama_provider.js';
|
|
34
|
+
export { AnthropicProvider } from '../anthropic_provider.js';
|
|
35
|
+
export type { LLMProvider, LLMResponse, GenerateOptions, ImageInput, ProviderCapabilities, ProviderUsageStats, ProviderConfig, OllamaConfig, AnthropicConfig, } from '../provider_interface.js';
|
|
36
|
+
export type { HybridConfig } from '../provider_factory.js';
|
|
37
|
+
export type { FeatureSpecification, BusinessScenario, SpecScreenshot, GeneratedTest, } from './types.js';
|
|
38
|
+
/**
|
|
39
|
+
* Version info
|
|
40
|
+
*/
|
|
41
|
+
export declare const VERSION = "2.0.0";
|
|
42
|
+
export declare const SUPPORTED_PLAYWRIGHT_VERSION = "1.56.0";
|
|
43
|
+
/**
|
|
44
|
+
* Feature flags
|
|
45
|
+
*/
|
|
46
|
+
export declare const FEATURES: {
|
|
47
|
+
readonly LLM_AGNOSTIC: true;
|
|
48
|
+
readonly SPECIFICATION_DRIVEN: true;
|
|
49
|
+
readonly PLAYWRIGHT_AGENTS: true;
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/e2e-test-gen/index.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAGH,OAAO,EAAC,mBAAmB,EAAC,MAAM,kBAAkB,CAAC;AACrD,YAAY,EAAC,WAAW,EAAE,kBAAkB,EAAC,MAAM,kBAAkB,CAAC;AAGtE,OAAO,EAAC,kBAAkB,EAAC,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAC,iBAAiB,EAAC,MAAM,0BAA0B,CAAC;AAE3D,YAAY,EACR,WAAW,EACX,WAAW,EACX,eAAe,EACf,UAAU,EACV,oBAAoB,EACpB,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,eAAe,GAClB,MAAM,0BAA0B,CAAC;AAClC,YAAY,EAAC,YAAY,EAAC,MAAM,wBAAwB,CAAC;AAGzD,YAAY,EAER,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EAGd,aAAa,GAChB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,eAAO,MAAM,OAAO,UAAU,CAAC;AAC/B,eAAO,MAAM,4BAA4B,WAAW,CAAC;AAErD;;GAEG;AACH,eAAO,MAAM,QAAQ;;;;CAIX,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
3
|
+
// See LICENSE.txt for license information.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.FEATURES = exports.SUPPORTED_PLAYWRIGHT_VERSION = exports.VERSION = exports.AnthropicProvider = exports.OllamaProvider = exports.LLMProviderFactory = exports.SpecificationParser = void 0;
|
|
6
|
+
/**
|
|
7
|
+
* Autonomous E2E Testing System
|
|
8
|
+
*
|
|
9
|
+
* A specification-driven testing system that bridges PDF/Markdown specs
|
|
10
|
+
* with Playwright's native agents for test planning, generation, and healing.
|
|
11
|
+
*
|
|
12
|
+
* Quick Start:
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import {SpecBridge, createAnthropicBridge} from '@mattermost/playwright-lib/autonomous';
|
|
15
|
+
*
|
|
16
|
+
* // Convert a specification to Playwright-compatible markdown
|
|
17
|
+
* const bridge = createAnthropicBridge(process.env.ANTHROPIC_API_KEY);
|
|
18
|
+
* const result = await bridge.convertToPlaywrightSpecs('spec.pdf', 'specs/');
|
|
19
|
+
*
|
|
20
|
+
* // Then use Playwright agents:
|
|
21
|
+
* // @planner explore http://localhost:8065
|
|
22
|
+
* // @generator create tests from specs/
|
|
23
|
+
* // @healer fix failing tests
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* Architecture:
|
|
27
|
+
* - SpecificationParser: Parses PDF/MD/JSON specs into structured format
|
|
28
|
+
* - SpecBridge: Converts specs to Playwright Agent-compatible markdown
|
|
29
|
+
* - LLM Providers: Pluggable AI providers (Anthropic, Ollama, OpenAI)
|
|
30
|
+
*
|
|
31
|
+
* The heavy lifting (test generation, execution, healing) is delegated to
|
|
32
|
+
* Playwright's built-in agents which are production-ready and maintained
|
|
33
|
+
* by the Playwright team.
|
|
34
|
+
*/
|
|
35
|
+
// Core Components
|
|
36
|
+
var spec_parser_js_1 = require("./spec_parser.js");
|
|
37
|
+
Object.defineProperty(exports, "SpecificationParser", { enumerable: true, get: function () { return spec_parser_js_1.SpecificationParser; } });
|
|
38
|
+
// LLM Providers (re-exported from parent package)
|
|
39
|
+
var provider_factory_js_1 = require("../provider_factory.js");
|
|
40
|
+
Object.defineProperty(exports, "LLMProviderFactory", { enumerable: true, get: function () { return provider_factory_js_1.LLMProviderFactory; } });
|
|
41
|
+
var ollama_provider_js_1 = require("../ollama_provider.js");
|
|
42
|
+
Object.defineProperty(exports, "OllamaProvider", { enumerable: true, get: function () { return ollama_provider_js_1.OllamaProvider; } });
|
|
43
|
+
var anthropic_provider_js_1 = require("../anthropic_provider.js");
|
|
44
|
+
Object.defineProperty(exports, "AnthropicProvider", { enumerable: true, get: function () { return anthropic_provider_js_1.AnthropicProvider; } });
|
|
45
|
+
/**
|
|
46
|
+
* Version info
|
|
47
|
+
*/
|
|
48
|
+
exports.VERSION = '2.0.0';
|
|
49
|
+
exports.SUPPORTED_PLAYWRIGHT_VERSION = '1.56.0';
|
|
50
|
+
/**
|
|
51
|
+
* Feature flags
|
|
52
|
+
*/
|
|
53
|
+
exports.FEATURES = {
|
|
54
|
+
LLM_AGNOSTIC: true,
|
|
55
|
+
SPECIFICATION_DRIVEN: true,
|
|
56
|
+
PLAYWRIGHT_AGENTS: true,
|
|
57
|
+
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { LLMProvider } from '../provider_interface.js';
|
|
2
|
+
import type { FeatureSpecification } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Optional cache interface for PDF parsing results
|
|
5
|
+
* Implementations can provide caching to avoid re-parsing PDFs
|
|
6
|
+
*/
|
|
7
|
+
export interface SpecificationCache {
|
|
8
|
+
isSpecificationCached(path: string, hash: string): boolean;
|
|
9
|
+
getCachedSpecifications(path: string, hash: string): FeatureSpecification[];
|
|
10
|
+
saveSpecification(spec: FeatureSpecification): void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Specification Parser
|
|
14
|
+
*
|
|
15
|
+
* Parses user-provided specification documents to guide autonomous testing.
|
|
16
|
+
* Supports multiple formats:
|
|
17
|
+
* - Markdown (.md) with embedded screenshots and Given-When-Then scenarios
|
|
18
|
+
* - JSON (.json) with structured feature definitions
|
|
19
|
+
* - PDF (.pdf) with text, diagrams, and screenshots extracted via LLM
|
|
20
|
+
* - Plain text focus strings (natural language)
|
|
21
|
+
*
|
|
22
|
+
* Extracts:
|
|
23
|
+
* - Feature name, description, priority
|
|
24
|
+
* - Target URLs to prioritize in crawling
|
|
25
|
+
* - Business scenarios (Given-When-Then)
|
|
26
|
+
* - Acceptance criteria
|
|
27
|
+
* - Reference screenshots for visual comparison
|
|
28
|
+
* - UI mockups and wireframes from PDFs
|
|
29
|
+
*
|
|
30
|
+
* The parsed specifications guide:
|
|
31
|
+
* - Crawler URL prioritization
|
|
32
|
+
* - Test scenario generation
|
|
33
|
+
* - Visual regression comparison
|
|
34
|
+
* - Coverage gap detection
|
|
35
|
+
*/
|
|
36
|
+
export declare class SpecificationParser {
|
|
37
|
+
private llmProvider;
|
|
38
|
+
private cache?;
|
|
39
|
+
constructor(llmProvider: LLMProvider, cache?: SpecificationCache);
|
|
40
|
+
/**
|
|
41
|
+
* Parse specification from file or string
|
|
42
|
+
*/
|
|
43
|
+
parse(source: string, sourceType?: 'file' | 'string'): Promise<FeatureSpecification[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Parse Markdown specification
|
|
46
|
+
*
|
|
47
|
+
* Expected format:
|
|
48
|
+
* # Feature: Feature Name
|
|
49
|
+
* **Priority**: High
|
|
50
|
+
* **Target URLs**: /path1, /path2
|
|
51
|
+
*
|
|
52
|
+
* ## Description
|
|
53
|
+
* Feature description...
|
|
54
|
+
*
|
|
55
|
+
* ## Business Scenarios
|
|
56
|
+
* ### Scenario 1: Name
|
|
57
|
+
* - **Given**: Precondition
|
|
58
|
+
* - **When**: Action
|
|
59
|
+
* - **Then**: Expected outcome
|
|
60
|
+
*
|
|
61
|
+
* ## Acceptance Criteria
|
|
62
|
+
* - Criterion 1
|
|
63
|
+
* - Criterion 2
|
|
64
|
+
*
|
|
65
|
+
* ## Screenshots
|
|
66
|
+
* 
|
|
67
|
+
*/
|
|
68
|
+
private parseMarkdown;
|
|
69
|
+
/**
|
|
70
|
+
* Parse JSON specification
|
|
71
|
+
*/
|
|
72
|
+
private parseJSON;
|
|
73
|
+
/**
|
|
74
|
+
* Parse PDF specification using LLM vision
|
|
75
|
+
*
|
|
76
|
+
* PDFs can contain:
|
|
77
|
+
* - Product requirement documents (PRDs)
|
|
78
|
+
* - Feature specifications with screenshots
|
|
79
|
+
* - UI mockups and wireframes
|
|
80
|
+
* - User flow diagrams
|
|
81
|
+
* - Acceptance criteria and test plans
|
|
82
|
+
*
|
|
83
|
+
* The LLM will extract structured information including:
|
|
84
|
+
* - Feature name and description
|
|
85
|
+
* - Business scenarios
|
|
86
|
+
* - Target URLs (inferred from screenshots/mockups)
|
|
87
|
+
* - Acceptance criteria
|
|
88
|
+
* - Screenshots/mockups for visual comparison
|
|
89
|
+
*/
|
|
90
|
+
private parsePDF;
|
|
91
|
+
/**
|
|
92
|
+
* Parse natural language focus string using LLM
|
|
93
|
+
*/
|
|
94
|
+
private parseFocusString;
|
|
95
|
+
/**
|
|
96
|
+
* Finalize partial spec
|
|
97
|
+
*/
|
|
98
|
+
private finalizeSpec;
|
|
99
|
+
/**
|
|
100
|
+
* Finalize partial scenario
|
|
101
|
+
*/
|
|
102
|
+
private finalizeScenario;
|
|
103
|
+
/**
|
|
104
|
+
* Generate spec ID from name
|
|
105
|
+
*/
|
|
106
|
+
private generateSpecId;
|
|
107
|
+
/**
|
|
108
|
+
* Resolve image path relative to spec file
|
|
109
|
+
*/
|
|
110
|
+
private resolveImagePath;
|
|
111
|
+
/**
|
|
112
|
+
* Load screenshot data from files
|
|
113
|
+
*/
|
|
114
|
+
private loadScreenshots;
|
|
115
|
+
/**
|
|
116
|
+
* Validate parsed specification
|
|
117
|
+
*/
|
|
118
|
+
validateSpec(spec: FeatureSpecification): {
|
|
119
|
+
valid: boolean;
|
|
120
|
+
errors: string[];
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Get spec coverage summary
|
|
124
|
+
*/
|
|
125
|
+
getSpecSummary(spec: FeatureSpecification): SpecSummary;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Specification summary
|
|
129
|
+
*/
|
|
130
|
+
export interface SpecSummary {
|
|
131
|
+
id: string;
|
|
132
|
+
name: string;
|
|
133
|
+
priority: string;
|
|
134
|
+
targetUrlCount: number;
|
|
135
|
+
scenarioCount: number;
|
|
136
|
+
mustHaveScenarios: number;
|
|
137
|
+
shouldHaveScenarios: number;
|
|
138
|
+
niceToHaveScenarios: number;
|
|
139
|
+
acceptanceCriteriaCount: number;
|
|
140
|
+
hasScreenshots: boolean;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=spec_parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec_parser.d.ts","sourceRoot":"","sources":["../../src/e2e-test-gen/spec_parser.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,0BAA0B,CAAC;AAE1D,OAAO,KAAK,EAAmB,oBAAoB,EAAiB,MAAM,YAAY,CAAC;AAEvF;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IAC/B,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3D,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAAC;IAC5E,iBAAiB,CAAC,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACvD;AASD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,mBAAmB;IAC5B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,KAAK,CAAC,CAAqB;gBAEvB,WAAW,EAAE,WAAW,EAAE,KAAK,CAAC,EAAE,kBAAkB;IAKhE;;OAEG;IACG,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,GAAE,MAAM,GAAG,QAAiB,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IA6BpG;;;;;;;;;;;;;;;;;;;;;;;OAuBG;YACW,aAAa;IAyI3B;;OAEG;IACH,OAAO,CAAC,SAAS;IAgFjB;;;;;;;;;;;;;;;;OAgBG;YACW,QAAQ;IA8TtB;;OAEG;YACW,gBAAgB;IA8E9B;;OAEG;IACH,OAAO,CAAC,YAAY;IAcpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAUxB;;OAEG;IACH,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAcxB;;OAEG;YACW,eAAe;IAmB7B;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,oBAAoB,GAAG;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,EAAE,CAAA;KAAC;IAwB5E;;OAEG;IACH,cAAc,CAAC,IAAI,EAAE,oBAAoB,GAAG,WAAW;CAc1D;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,uBAAuB,EAAE,MAAM,CAAC;IAChC,cAAc,EAAE,OAAO,CAAC;CAC3B"}
|