mindheal 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +48 -0
- package/CHANGELOG.md +27 -0
- package/LICENSE +21 -0
- package/README.md +481 -0
- package/dist/cjs/ai/ai-provider.js +46 -0
- package/dist/cjs/ai/ai-provider.js.map +1 -0
- package/dist/cjs/ai/anthropic-provider.js +106 -0
- package/dist/cjs/ai/anthropic-provider.js.map +1 -0
- package/dist/cjs/ai/azure-openai-provider.js +130 -0
- package/dist/cjs/ai/azure-openai-provider.js.map +1 -0
- package/dist/cjs/ai/bedrock-provider.js +183 -0
- package/dist/cjs/ai/bedrock-provider.js.map +1 -0
- package/dist/cjs/ai/deepseek-provider.js +118 -0
- package/dist/cjs/ai/deepseek-provider.js.map +1 -0
- package/dist/cjs/ai/gemini-provider.js +129 -0
- package/dist/cjs/ai/gemini-provider.js.map +1 -0
- package/dist/cjs/ai/groq-provider.js +118 -0
- package/dist/cjs/ai/groq-provider.js.map +1 -0
- package/dist/cjs/ai/meta-provider.js +118 -0
- package/dist/cjs/ai/meta-provider.js.map +1 -0
- package/dist/cjs/ai/ollama-provider.js +127 -0
- package/dist/cjs/ai/ollama-provider.js.map +1 -0
- package/dist/cjs/ai/openai-provider.js +117 -0
- package/dist/cjs/ai/openai-provider.js.map +1 -0
- package/dist/cjs/ai/perplexity-provider.js +118 -0
- package/dist/cjs/ai/perplexity-provider.js.map +1 -0
- package/dist/cjs/ai/prompt-templates.js +174 -0
- package/dist/cjs/ai/prompt-templates.js.map +1 -0
- package/dist/cjs/ai/qwen-provider.js +118 -0
- package/dist/cjs/ai/qwen-provider.js.map +1 -0
- package/dist/cjs/analytics/healing-analytics.js +263 -0
- package/dist/cjs/analytics/healing-analytics.js.map +1 -0
- package/dist/cjs/cli/init.js +517 -0
- package/dist/cjs/cli/init.js.map +1 -0
- package/dist/cjs/config/config-loader.js +135 -0
- package/dist/cjs/config/config-loader.js.map +1 -0
- package/dist/cjs/config/defaults.js +109 -0
- package/dist/cjs/config/defaults.js.map +1 -0
- package/dist/cjs/core/dom-snapshot.js +280 -0
- package/dist/cjs/core/dom-snapshot.js.map +1 -0
- package/dist/cjs/core/enterprise-strategy.js +702 -0
- package/dist/cjs/core/enterprise-strategy.js.map +1 -0
- package/dist/cjs/core/healer.js +283 -0
- package/dist/cjs/core/healer.js.map +1 -0
- package/dist/cjs/core/interceptor.js +945 -0
- package/dist/cjs/core/interceptor.js.map +1 -0
- package/dist/cjs/core/locator-analyzer.js +172 -0
- package/dist/cjs/core/locator-analyzer.js.map +1 -0
- package/dist/cjs/core/locator-strategies.js +891 -0
- package/dist/cjs/core/locator-strategies.js.map +1 -0
- package/dist/cjs/core/self-heal-cache.js +178 -0
- package/dist/cjs/core/self-heal-cache.js.map +1 -0
- package/dist/cjs/core/smart-retry.js +248 -0
- package/dist/cjs/core/smart-retry.js.map +1 -0
- package/dist/cjs/core/visual-verification.js +262 -0
- package/dist/cjs/core/visual-verification.js.map +1 -0
- package/dist/cjs/git/code-modifier.js +184 -0
- package/dist/cjs/git/code-modifier.js.map +1 -0
- package/dist/cjs/git/git-operations.js +145 -0
- package/dist/cjs/git/git-operations.js.map +1 -0
- package/dist/cjs/git/pr-creator.js +190 -0
- package/dist/cjs/git/pr-creator.js.map +1 -0
- package/dist/cjs/index.js +97 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/rag/context-retriever.js +289 -0
- package/dist/cjs/rag/context-retriever.js.map +1 -0
- package/dist/cjs/rag/embeddings.js +82 -0
- package/dist/cjs/rag/embeddings.js.map +1 -0
- package/dist/cjs/rag/knowledge-store.js +159 -0
- package/dist/cjs/rag/knowledge-store.js.map +1 -0
- package/dist/cjs/reporters/heal-report.js +279 -0
- package/dist/cjs/reporters/heal-report.js.map +1 -0
- package/dist/cjs/reporters/heal-reporter.js +294 -0
- package/dist/cjs/reporters/heal-reporter.js.map +1 -0
- package/dist/cjs/server/review-server.js +166 -0
- package/dist/cjs/server/review-server.js.map +1 -0
- package/dist/cjs/server/routes.js +92 -0
- package/dist/cjs/server/routes.js.map +1 -0
- package/dist/cjs/utils/environment.js +57 -0
- package/dist/cjs/utils/environment.js.map +1 -0
- package/dist/cjs/utils/file-lock.js +136 -0
- package/dist/cjs/utils/file-lock.js.map +1 -0
- package/dist/cjs/utils/file-utils.js +49 -0
- package/dist/cjs/utils/file-utils.js.map +1 -0
- package/dist/cjs/utils/logger.js +78 -0
- package/dist/cjs/utils/logger.js.map +1 -0
- package/dist/esm/ai/ai-provider.js +44 -0
- package/dist/esm/ai/ai-provider.js.map +1 -0
- package/dist/esm/ai/anthropic-provider.js +104 -0
- package/dist/esm/ai/anthropic-provider.js.map +1 -0
- package/dist/esm/ai/azure-openai-provider.js +128 -0
- package/dist/esm/ai/azure-openai-provider.js.map +1 -0
- package/dist/esm/ai/bedrock-provider.js +181 -0
- package/dist/esm/ai/bedrock-provider.js.map +1 -0
- package/dist/esm/ai/deepseek-provider.js +116 -0
- package/dist/esm/ai/deepseek-provider.js.map +1 -0
- package/dist/esm/ai/gemini-provider.js +127 -0
- package/dist/esm/ai/gemini-provider.js.map +1 -0
- package/dist/esm/ai/groq-provider.js +116 -0
- package/dist/esm/ai/groq-provider.js.map +1 -0
- package/dist/esm/ai/meta-provider.js +116 -0
- package/dist/esm/ai/meta-provider.js.map +1 -0
- package/dist/esm/ai/ollama-provider.js +125 -0
- package/dist/esm/ai/ollama-provider.js.map +1 -0
- package/dist/esm/ai/openai-provider.js +115 -0
- package/dist/esm/ai/openai-provider.js.map +1 -0
- package/dist/esm/ai/perplexity-provider.js +116 -0
- package/dist/esm/ai/perplexity-provider.js.map +1 -0
- package/dist/esm/ai/prompt-templates.js +171 -0
- package/dist/esm/ai/prompt-templates.js.map +1 -0
- package/dist/esm/ai/qwen-provider.js +116 -0
- package/dist/esm/ai/qwen-provider.js.map +1 -0
- package/dist/esm/analytics/healing-analytics.js +261 -0
- package/dist/esm/analytics/healing-analytics.js.map +1 -0
- package/dist/esm/cli/init.js +495 -0
- package/dist/esm/cli/init.js.map +1 -0
- package/dist/esm/config/config-loader.js +132 -0
- package/dist/esm/config/config-loader.js.map +1 -0
- package/dist/esm/config/defaults.js +107 -0
- package/dist/esm/config/defaults.js.map +1 -0
- package/dist/esm/core/dom-snapshot.js +278 -0
- package/dist/esm/core/dom-snapshot.js.map +1 -0
- package/dist/esm/core/enterprise-strategy.js +695 -0
- package/dist/esm/core/enterprise-strategy.js.map +1 -0
- package/dist/esm/core/healer.js +281 -0
- package/dist/esm/core/healer.js.map +1 -0
- package/dist/esm/core/interceptor.js +940 -0
- package/dist/esm/core/interceptor.js.map +1 -0
- package/dist/esm/core/locator-analyzer.js +169 -0
- package/dist/esm/core/locator-analyzer.js.map +1 -0
- package/dist/esm/core/locator-strategies.js +882 -0
- package/dist/esm/core/locator-strategies.js.map +1 -0
- package/dist/esm/core/self-heal-cache.js +176 -0
- package/dist/esm/core/self-heal-cache.js.map +1 -0
- package/dist/esm/core/smart-retry.js +246 -0
- package/dist/esm/core/smart-retry.js.map +1 -0
- package/dist/esm/core/visual-verification.js +260 -0
- package/dist/esm/core/visual-verification.js.map +1 -0
- package/dist/esm/git/code-modifier.js +182 -0
- package/dist/esm/git/code-modifier.js.map +1 -0
- package/dist/esm/git/git-operations.js +143 -0
- package/dist/esm/git/git-operations.js.map +1 -0
- package/dist/esm/git/pr-creator.js +188 -0
- package/dist/esm/git/pr-creator.js.map +1 -0
- package/dist/esm/index.js +37 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/rag/context-retriever.js +287 -0
- package/dist/esm/rag/context-retriever.js.map +1 -0
- package/dist/esm/rag/embeddings.js +77 -0
- package/dist/esm/rag/embeddings.js.map +1 -0
- package/dist/esm/rag/knowledge-store.js +157 -0
- package/dist/esm/rag/knowledge-store.js.map +1 -0
- package/dist/esm/reporters/heal-report.js +277 -0
- package/dist/esm/reporters/heal-report.js.map +1 -0
- package/dist/esm/reporters/heal-reporter.js +290 -0
- package/dist/esm/reporters/heal-reporter.js.map +1 -0
- package/dist/esm/server/review-server.js +164 -0
- package/dist/esm/server/review-server.js.map +1 -0
- package/dist/esm/server/routes.js +90 -0
- package/dist/esm/server/routes.js.map +1 -0
- package/dist/esm/utils/environment.js +53 -0
- package/dist/esm/utils/environment.js.map +1 -0
- package/dist/esm/utils/file-lock.js +134 -0
- package/dist/esm/utils/file-lock.js.map +1 -0
- package/dist/esm/utils/file-utils.js +43 -0
- package/dist/esm/utils/file-utils.js.map +1 -0
- package/dist/esm/utils/logger.js +75 -0
- package/dist/esm/utils/logger.js.map +1 -0
- package/dist/types/ai/ai-provider.d.ts +4 -0
- package/dist/types/ai/ai-provider.d.ts.map +1 -0
- package/dist/types/ai/anthropic-provider.d.ts +11 -0
- package/dist/types/ai/anthropic-provider.d.ts.map +1 -0
- package/dist/types/ai/azure-openai-provider.d.ts +13 -0
- package/dist/types/ai/azure-openai-provider.d.ts.map +1 -0
- package/dist/types/ai/bedrock-provider.d.ts +14 -0
- package/dist/types/ai/bedrock-provider.d.ts.map +1 -0
- package/dist/types/ai/deepseek-provider.d.ts +12 -0
- package/dist/types/ai/deepseek-provider.d.ts.map +1 -0
- package/dist/types/ai/gemini-provider.d.ts +12 -0
- package/dist/types/ai/gemini-provider.d.ts.map +1 -0
- package/dist/types/ai/groq-provider.d.ts +12 -0
- package/dist/types/ai/groq-provider.d.ts.map +1 -0
- package/dist/types/ai/meta-provider.d.ts +12 -0
- package/dist/types/ai/meta-provider.d.ts.map +1 -0
- package/dist/types/ai/ollama-provider.d.ts +10 -0
- package/dist/types/ai/ollama-provider.d.ts.map +1 -0
- package/dist/types/ai/openai-provider.d.ts +11 -0
- package/dist/types/ai/openai-provider.d.ts.map +1 -0
- package/dist/types/ai/perplexity-provider.d.ts +12 -0
- package/dist/types/ai/perplexity-provider.d.ts.map +1 -0
- package/dist/types/ai/prompt-templates.d.ts +11 -0
- package/dist/types/ai/prompt-templates.d.ts.map +1 -0
- package/dist/types/ai/qwen-provider.d.ts +12 -0
- package/dist/types/ai/qwen-provider.d.ts.map +1 -0
- package/dist/types/analytics/healing-analytics.d.ts +36 -0
- package/dist/types/analytics/healing-analytics.d.ts.map +1 -0
- package/dist/types/cli/init.d.ts +15 -0
- package/dist/types/cli/init.d.ts.map +1 -0
- package/dist/types/config/config-loader.d.ts +4 -0
- package/dist/types/config/config-loader.d.ts.map +1 -0
- package/dist/types/config/defaults.d.ts +3 -0
- package/dist/types/config/defaults.d.ts.map +1 -0
- package/dist/types/core/dom-snapshot.d.ts +12 -0
- package/dist/types/core/dom-snapshot.d.ts.map +1 -0
- package/dist/types/core/enterprise-strategy.d.ts +56 -0
- package/dist/types/core/enterprise-strategy.d.ts.map +1 -0
- package/dist/types/core/healer.d.ts +52 -0
- package/dist/types/core/healer.d.ts.map +1 -0
- package/dist/types/core/interceptor.d.ts +64 -0
- package/dist/types/core/interceptor.d.ts.map +1 -0
- package/dist/types/core/locator-analyzer.d.ts +31 -0
- package/dist/types/core/locator-analyzer.d.ts.map +1 -0
- package/dist/types/core/locator-strategies.d.ts +45 -0
- package/dist/types/core/locator-strategies.d.ts.map +1 -0
- package/dist/types/core/self-heal-cache.d.ts +51 -0
- package/dist/types/core/self-heal-cache.d.ts.map +1 -0
- package/dist/types/core/smart-retry.d.ts +64 -0
- package/dist/types/core/smart-retry.d.ts.map +1 -0
- package/dist/types/core/visual-verification.d.ts +46 -0
- package/dist/types/core/visual-verification.d.ts.map +1 -0
- package/dist/types/git/code-modifier.d.ts +51 -0
- package/dist/types/git/code-modifier.d.ts.map +1 -0
- package/dist/types/git/git-operations.d.ts +40 -0
- package/dist/types/git/git-operations.d.ts.map +1 -0
- package/dist/types/git/pr-creator.d.ts +27 -0
- package/dist/types/git/pr-creator.d.ts.map +1 -0
- package/dist/types/index.d.ts +40 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/rag/context-retriever.d.ts +69 -0
- package/dist/types/rag/context-retriever.d.ts.map +1 -0
- package/dist/types/rag/embeddings.d.ts +32 -0
- package/dist/types/rag/embeddings.d.ts.map +1 -0
- package/dist/types/rag/index.d.ts +12 -0
- package/dist/types/rag/index.d.ts.map +1 -0
- package/dist/types/rag/knowledge-store.d.ts +38 -0
- package/dist/types/rag/knowledge-store.d.ts.map +1 -0
- package/dist/types/reporters/heal-report.d.ts +29 -0
- package/dist/types/reporters/heal-report.d.ts.map +1 -0
- package/dist/types/reporters/heal-reporter.d.ts +49 -0
- package/dist/types/reporters/heal-reporter.d.ts.map +1 -0
- package/dist/types/server/review-server.d.ts +20 -0
- package/dist/types/server/review-server.d.ts.map +1 -0
- package/dist/types/server/routes.d.ts +4 -0
- package/dist/types/server/routes.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +433 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/utils/environment.d.ts +10 -0
- package/dist/types/utils/environment.d.ts.map +1 -0
- package/dist/types/utils/file-lock.d.ts +37 -0
- package/dist/types/utils/file-lock.d.ts.map +1 -0
- package/dist/types/utils/file-utils.d.ts +7 -0
- package/dist/types/utils/file-utils.d.ts.map +1 -0
- package/dist/types/utils/logger.d.ts +9 -0
- package/dist/types/utils/logger.d.ts.map +1 -0
- package/package.json +106 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { buildHealingPrompt, parseHealingResponse } from './prompt-templates.js';
|
|
2
|
+
import { logger } from '../utils/logger.js';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_MODEL = 'meta-llama/Llama-3.3-70B-Instruct-Turbo';
|
|
5
|
+
const DEFAULT_MAX_TOKENS = 1024;
|
|
6
|
+
const DEFAULT_TEMPERATURE = 0;
|
|
7
|
+
const DEFAULT_API_URL = 'https://api.together.xyz/v1/chat/completions';
|
|
8
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
9
|
+
class MetaProvider {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.name = 'meta';
|
|
12
|
+
if (!config.apiKey) {
|
|
13
|
+
throw new Error('[MindHeal] Meta/Llama API key is required');
|
|
14
|
+
}
|
|
15
|
+
this.apiKey = config.apiKey;
|
|
16
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
17
|
+
this.maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
18
|
+
this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;
|
|
19
|
+
this.apiUrl = config.baseUrl ?? DEFAULT_API_URL;
|
|
20
|
+
}
|
|
21
|
+
async suggestLocator(request) {
|
|
22
|
+
const fullPrompt = buildHealingPrompt(request);
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
logger.debug('Meta/Llama API: sending healing request', {
|
|
25
|
+
model: this.model,
|
|
26
|
+
originalLocator: request.originalLocator.selector,
|
|
27
|
+
pageUrl: request.pageUrl,
|
|
28
|
+
});
|
|
29
|
+
// Split the combined prompt into system + user messages at the separator
|
|
30
|
+
const separatorIndex = fullPrompt.indexOf('\n\n---\n\n');
|
|
31
|
+
let systemContent;
|
|
32
|
+
let userContent;
|
|
33
|
+
if (separatorIndex !== -1) {
|
|
34
|
+
systemContent = fullPrompt.slice(0, separatorIndex);
|
|
35
|
+
userContent = fullPrompt.slice(separatorIndex + 7); // length of '\n\n---\n\n'
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
systemContent = 'You are an expert Playwright test engineer. Respond with only valid JSON.';
|
|
39
|
+
userContent = fullPrompt;
|
|
40
|
+
}
|
|
41
|
+
const messages = [
|
|
42
|
+
{ role: 'system', content: systemContent },
|
|
43
|
+
{ role: 'user', content: userContent },
|
|
44
|
+
];
|
|
45
|
+
const body = JSON.stringify({
|
|
46
|
+
model: this.model,
|
|
47
|
+
max_tokens: this.maxTokens,
|
|
48
|
+
temperature: this.temperature,
|
|
49
|
+
messages,
|
|
50
|
+
});
|
|
51
|
+
let responseData;
|
|
52
|
+
try {
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
55
|
+
const response = await fetch(this.apiUrl, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: {
|
|
58
|
+
'content-type': 'application/json',
|
|
59
|
+
'authorization': `Bearer ${this.apiKey}`,
|
|
60
|
+
},
|
|
61
|
+
body,
|
|
62
|
+
signal: controller.signal,
|
|
63
|
+
});
|
|
64
|
+
clearTimeout(timeoutId);
|
|
65
|
+
if (response.status === 429) {
|
|
66
|
+
const retryAfter = response.headers.get('retry-after');
|
|
67
|
+
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 5;
|
|
68
|
+
logger.warn(`Meta/Llama API: rate limited, retrying after ${waitSeconds}s`);
|
|
69
|
+
await sleep(waitSeconds * 1000);
|
|
70
|
+
return this.suggestLocator(request);
|
|
71
|
+
}
|
|
72
|
+
responseData = (await response.json());
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
const errorResp = responseData;
|
|
75
|
+
throw new Error(`Meta/Llama API error (${response.status}): ${errorResp.error?.message ?? response.statusText}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
80
|
+
throw new Error(`[MindHeal] Meta/Llama API request timed out after ${REQUEST_TIMEOUT_MS}ms`);
|
|
81
|
+
}
|
|
82
|
+
if (error instanceof Error && error.message.startsWith('Meta/Llama API error')) {
|
|
83
|
+
throw new Error(`[MindHeal] ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
throw new Error(`[MindHeal] Meta/Llama API request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
86
|
+
}
|
|
87
|
+
const successResp = responseData;
|
|
88
|
+
const choice = successResp.choices[0];
|
|
89
|
+
if (!choice?.message?.content) {
|
|
90
|
+
throw new Error('[MindHeal] Meta/Llama API returned empty response');
|
|
91
|
+
}
|
|
92
|
+
const duration = Date.now() - startTime;
|
|
93
|
+
logger.debug('Meta/Llama API: received response', {
|
|
94
|
+
model: successResp.model,
|
|
95
|
+
duration: `${duration}ms`,
|
|
96
|
+
promptTokens: successResp.usage.prompt_tokens,
|
|
97
|
+
completionTokens: successResp.usage.completion_tokens,
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
return parseHealingResponse(choice.message.content);
|
|
101
|
+
}
|
|
102
|
+
catch (parseError) {
|
|
103
|
+
logger.error('Meta/Llama API: failed to parse response', {
|
|
104
|
+
rawText: choice.message.content.slice(0, 500),
|
|
105
|
+
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
106
|
+
});
|
|
107
|
+
throw parseError;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function sleep(ms) {
|
|
112
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { MetaProvider };
|
|
116
|
+
//# sourceMappingURL=meta-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta-provider.js","sources":["../../../../src/ai/meta-provider.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { buildHealingPrompt, parseHealingResponse } from './prompt-templates.js';
|
|
2
|
+
import { logger } from '../utils/logger.js';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_HOST = 'http://localhost:11434';
|
|
5
|
+
const DEFAULT_MODEL = 'llama3';
|
|
6
|
+
const DEFAULT_TEMPERATURE = 0.1;
|
|
7
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
8
|
+
class OllamaProvider {
|
|
9
|
+
constructor(config) {
|
|
10
|
+
this.name = 'ollama';
|
|
11
|
+
this.host = config.ollamaHost ?? config.baseUrl ?? DEFAULT_HOST;
|
|
12
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
13
|
+
this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;
|
|
14
|
+
// Strip trailing slash for consistency
|
|
15
|
+
if (this.host.endsWith('/')) {
|
|
16
|
+
this.host = this.host.slice(0, -1);
|
|
17
|
+
}
|
|
18
|
+
logger.debug('OllamaProvider initialized', {
|
|
19
|
+
host: this.host,
|
|
20
|
+
model: this.model,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async suggestLocator(request) {
|
|
24
|
+
const fullPrompt = buildHealingPrompt(request);
|
|
25
|
+
const startTime = Date.now();
|
|
26
|
+
logger.debug('Ollama API: sending healing request', {
|
|
27
|
+
model: this.model,
|
|
28
|
+
host: this.host,
|
|
29
|
+
originalLocator: request.originalLocator.selector,
|
|
30
|
+
pageUrl: request.pageUrl,
|
|
31
|
+
});
|
|
32
|
+
// Split the combined prompt into system + user messages at the separator
|
|
33
|
+
const separatorIndex = fullPrompt.indexOf('\n\n---\n\n');
|
|
34
|
+
let systemContent;
|
|
35
|
+
let userContent;
|
|
36
|
+
if (separatorIndex !== -1) {
|
|
37
|
+
systemContent = fullPrompt.slice(0, separatorIndex);
|
|
38
|
+
userContent = fullPrompt.slice(separatorIndex + 7); // length of '\n\n---\n\n'
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
systemContent = 'You are an expert Playwright test engineer. Respond with only valid JSON.';
|
|
42
|
+
userContent = fullPrompt;
|
|
43
|
+
}
|
|
44
|
+
const messages = [
|
|
45
|
+
{ role: 'system', content: systemContent },
|
|
46
|
+
{ role: 'user', content: userContent },
|
|
47
|
+
];
|
|
48
|
+
const body = JSON.stringify({
|
|
49
|
+
model: this.model,
|
|
50
|
+
messages,
|
|
51
|
+
stream: false,
|
|
52
|
+
options: {
|
|
53
|
+
temperature: this.temperature,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
const url = `${this.host}/api/chat`;
|
|
57
|
+
let responseData;
|
|
58
|
+
try {
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
61
|
+
const response = await fetch(url, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: {
|
|
64
|
+
'content-type': 'application/json',
|
|
65
|
+
},
|
|
66
|
+
body,
|
|
67
|
+
signal: controller.signal,
|
|
68
|
+
});
|
|
69
|
+
clearTimeout(timeoutId);
|
|
70
|
+
if (response.status === 429) {
|
|
71
|
+
const retryAfter = response.headers.get('retry-after');
|
|
72
|
+
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 5;
|
|
73
|
+
logger.warn(`Ollama API: rate limited, retrying after ${waitSeconds}s`);
|
|
74
|
+
await sleep(waitSeconds * 1000);
|
|
75
|
+
return this.suggestLocator(request);
|
|
76
|
+
}
|
|
77
|
+
responseData = (await response.json());
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
const errorResp = responseData;
|
|
80
|
+
throw new Error(`Ollama API error (${response.status}): ${errorResp.error ?? response.statusText}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
85
|
+
throw new Error(`[MindHeal] Ollama API request timed out after ${REQUEST_TIMEOUT_MS}ms`);
|
|
86
|
+
}
|
|
87
|
+
if (error instanceof Error && error.message.startsWith('Ollama API error')) {
|
|
88
|
+
throw new Error(`[MindHeal] ${error.message}`);
|
|
89
|
+
}
|
|
90
|
+
// Ollama is local — provide a helpful connection error message
|
|
91
|
+
if (error instanceof TypeError && (error.message.includes('fetch') || error.message.includes('ECONNREFUSED'))) {
|
|
92
|
+
throw new Error(`[MindHeal] Cannot connect to Ollama at ${this.host}. Ensure Ollama is running locally (https://ollama.com).`);
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`[MindHeal] Ollama API request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
95
|
+
}
|
|
96
|
+
const successResp = responseData;
|
|
97
|
+
const content = successResp.message?.content;
|
|
98
|
+
if (!content) {
|
|
99
|
+
throw new Error('[MindHeal] Ollama API returned empty response');
|
|
100
|
+
}
|
|
101
|
+
const duration = Date.now() - startTime;
|
|
102
|
+
logger.debug('Ollama API: received response', {
|
|
103
|
+
model: successResp.model,
|
|
104
|
+
duration: `${duration}ms`,
|
|
105
|
+
evalCount: successResp.eval_count,
|
|
106
|
+
promptEvalCount: successResp.prompt_eval_count,
|
|
107
|
+
});
|
|
108
|
+
try {
|
|
109
|
+
return parseHealingResponse(content);
|
|
110
|
+
}
|
|
111
|
+
catch (parseError) {
|
|
112
|
+
logger.error('Ollama API: failed to parse response', {
|
|
113
|
+
rawText: content.slice(0, 500),
|
|
114
|
+
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
115
|
+
});
|
|
116
|
+
throw parseError;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function sleep(ms) {
|
|
121
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { OllamaProvider };
|
|
125
|
+
//# sourceMappingURL=ollama-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ollama-provider.js","sources":["../../../../src/ai/ollama-provider.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { buildHealingPrompt, parseHealingResponse } from './prompt-templates.js';
|
|
2
|
+
import { logger } from '../utils/logger.js';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_MODEL = 'gpt-4o';
|
|
5
|
+
const DEFAULT_MAX_TOKENS = 1024;
|
|
6
|
+
const DEFAULT_TEMPERATURE = 0;
|
|
7
|
+
const API_URL = 'https://api.openai.com/v1/chat/completions';
|
|
8
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
9
|
+
class OpenAIProvider {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.name = 'openai';
|
|
12
|
+
if (!config.apiKey) {
|
|
13
|
+
throw new Error('[MindHeal] OpenAI API key is required');
|
|
14
|
+
}
|
|
15
|
+
this.apiKey = config.apiKey;
|
|
16
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
17
|
+
this.maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
18
|
+
this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;
|
|
19
|
+
}
|
|
20
|
+
async suggestLocator(request) {
|
|
21
|
+
const fullPrompt = buildHealingPrompt(request);
|
|
22
|
+
const startTime = Date.now();
|
|
23
|
+
logger.debug('OpenAI API: sending healing request', {
|
|
24
|
+
model: this.model,
|
|
25
|
+
originalLocator: request.originalLocator.selector,
|
|
26
|
+
pageUrl: request.pageUrl,
|
|
27
|
+
});
|
|
28
|
+
// Split the combined prompt into system + user messages at the separator
|
|
29
|
+
const separatorIndex = fullPrompt.indexOf('\n\n---\n\n');
|
|
30
|
+
let systemContent;
|
|
31
|
+
let userContent;
|
|
32
|
+
if (separatorIndex !== -1) {
|
|
33
|
+
systemContent = fullPrompt.slice(0, separatorIndex);
|
|
34
|
+
userContent = fullPrompt.slice(separatorIndex + 7); // length of '\n\n---\n\n'
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
systemContent = 'You are an expert Playwright test engineer. Respond with only valid JSON.';
|
|
38
|
+
userContent = fullPrompt;
|
|
39
|
+
}
|
|
40
|
+
const messages = [
|
|
41
|
+
{ role: 'system', content: systemContent },
|
|
42
|
+
{ role: 'user', content: userContent },
|
|
43
|
+
];
|
|
44
|
+
const body = JSON.stringify({
|
|
45
|
+
model: this.model,
|
|
46
|
+
max_tokens: this.maxTokens,
|
|
47
|
+
temperature: this.temperature,
|
|
48
|
+
messages,
|
|
49
|
+
});
|
|
50
|
+
let responseData;
|
|
51
|
+
try {
|
|
52
|
+
const controller = new AbortController();
|
|
53
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
54
|
+
const response = await fetch(API_URL, {
|
|
55
|
+
method: 'POST',
|
|
56
|
+
headers: {
|
|
57
|
+
'content-type': 'application/json',
|
|
58
|
+
'authorization': `Bearer ${this.apiKey}`,
|
|
59
|
+
},
|
|
60
|
+
body,
|
|
61
|
+
signal: controller.signal,
|
|
62
|
+
});
|
|
63
|
+
clearTimeout(timeoutId);
|
|
64
|
+
if (response.status === 429) {
|
|
65
|
+
const retryAfter = response.headers.get('retry-after');
|
|
66
|
+
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 5;
|
|
67
|
+
logger.warn(`OpenAI API: rate limited, retrying after ${waitSeconds}s`);
|
|
68
|
+
await sleep(waitSeconds * 1000);
|
|
69
|
+
return this.suggestLocator(request);
|
|
70
|
+
}
|
|
71
|
+
responseData = (await response.json());
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
const errorResp = responseData;
|
|
74
|
+
throw new Error(`OpenAI API error (${response.status}): ${errorResp.error?.message ?? response.statusText}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
79
|
+
throw new Error(`[MindHeal] OpenAI API request timed out after ${REQUEST_TIMEOUT_MS}ms`);
|
|
80
|
+
}
|
|
81
|
+
if (error instanceof Error && error.message.startsWith('OpenAI API error')) {
|
|
82
|
+
throw new Error(`[MindHeal] ${error.message}`);
|
|
83
|
+
}
|
|
84
|
+
throw new Error(`[MindHeal] OpenAI API request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
85
|
+
}
|
|
86
|
+
const successResp = responseData;
|
|
87
|
+
const choice = successResp.choices[0];
|
|
88
|
+
if (!choice?.message?.content) {
|
|
89
|
+
throw new Error('[MindHeal] OpenAI API returned empty response');
|
|
90
|
+
}
|
|
91
|
+
const duration = Date.now() - startTime;
|
|
92
|
+
logger.debug('OpenAI API: received response', {
|
|
93
|
+
model: successResp.model,
|
|
94
|
+
duration: `${duration}ms`,
|
|
95
|
+
promptTokens: successResp.usage.prompt_tokens,
|
|
96
|
+
completionTokens: successResp.usage.completion_tokens,
|
|
97
|
+
});
|
|
98
|
+
try {
|
|
99
|
+
return parseHealingResponse(choice.message.content);
|
|
100
|
+
}
|
|
101
|
+
catch (parseError) {
|
|
102
|
+
logger.error('OpenAI API: failed to parse response', {
|
|
103
|
+
rawText: choice.message.content.slice(0, 500),
|
|
104
|
+
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
105
|
+
});
|
|
106
|
+
throw parseError;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function sleep(ms) {
|
|
111
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export { OpenAIProvider };
|
|
115
|
+
//# sourceMappingURL=openai-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-provider.js","sources":["../../../../src/ai/openai-provider.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { buildHealingPrompt, parseHealingResponse } from './prompt-templates.js';
|
|
2
|
+
import { logger } from '../utils/logger.js';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_MODEL = 'sonar-pro';
|
|
5
|
+
const DEFAULT_MAX_TOKENS = 1024;
|
|
6
|
+
const DEFAULT_TEMPERATURE = 0;
|
|
7
|
+
const DEFAULT_API_URL = 'https://api.perplexity.ai/chat/completions';
|
|
8
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
9
|
+
class PerplexityProvider {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.name = 'perplexity';
|
|
12
|
+
if (!config.apiKey) {
|
|
13
|
+
throw new Error('[MindHeal] Perplexity API key is required');
|
|
14
|
+
}
|
|
15
|
+
this.apiKey = config.apiKey;
|
|
16
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
17
|
+
this.maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
18
|
+
this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;
|
|
19
|
+
this.apiUrl = config.baseUrl ?? DEFAULT_API_URL;
|
|
20
|
+
}
|
|
21
|
+
async suggestLocator(request) {
|
|
22
|
+
const fullPrompt = buildHealingPrompt(request);
|
|
23
|
+
const startTime = Date.now();
|
|
24
|
+
logger.debug('Perplexity API: sending healing request', {
|
|
25
|
+
model: this.model,
|
|
26
|
+
originalLocator: request.originalLocator.selector,
|
|
27
|
+
pageUrl: request.pageUrl,
|
|
28
|
+
});
|
|
29
|
+
// Split the combined prompt into system + user messages at the separator
|
|
30
|
+
const separatorIndex = fullPrompt.indexOf('\n\n---\n\n');
|
|
31
|
+
let systemContent;
|
|
32
|
+
let userContent;
|
|
33
|
+
if (separatorIndex !== -1) {
|
|
34
|
+
systemContent = fullPrompt.slice(0, separatorIndex);
|
|
35
|
+
userContent = fullPrompt.slice(separatorIndex + 7); // length of '\n\n---\n\n'
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
systemContent = 'You are an expert Playwright test engineer. Respond with only valid JSON.';
|
|
39
|
+
userContent = fullPrompt;
|
|
40
|
+
}
|
|
41
|
+
const messages = [
|
|
42
|
+
{ role: 'system', content: systemContent },
|
|
43
|
+
{ role: 'user', content: userContent },
|
|
44
|
+
];
|
|
45
|
+
const body = JSON.stringify({
|
|
46
|
+
model: this.model,
|
|
47
|
+
max_tokens: this.maxTokens,
|
|
48
|
+
temperature: this.temperature,
|
|
49
|
+
messages,
|
|
50
|
+
});
|
|
51
|
+
let responseData;
|
|
52
|
+
try {
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
55
|
+
const response = await fetch(this.apiUrl, {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: {
|
|
58
|
+
'content-type': 'application/json',
|
|
59
|
+
'authorization': `Bearer ${this.apiKey}`,
|
|
60
|
+
},
|
|
61
|
+
body,
|
|
62
|
+
signal: controller.signal,
|
|
63
|
+
});
|
|
64
|
+
clearTimeout(timeoutId);
|
|
65
|
+
if (response.status === 429) {
|
|
66
|
+
const retryAfter = response.headers.get('retry-after');
|
|
67
|
+
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 5;
|
|
68
|
+
logger.warn(`Perplexity API: rate limited, retrying after ${waitSeconds}s`);
|
|
69
|
+
await sleep(waitSeconds * 1000);
|
|
70
|
+
return this.suggestLocator(request);
|
|
71
|
+
}
|
|
72
|
+
responseData = (await response.json());
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
const errorResp = responseData;
|
|
75
|
+
throw new Error(`Perplexity API error (${response.status}): ${errorResp.error?.message ?? response.statusText}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
80
|
+
throw new Error(`[MindHeal] Perplexity API request timed out after ${REQUEST_TIMEOUT_MS}ms`);
|
|
81
|
+
}
|
|
82
|
+
if (error instanceof Error && error.message.startsWith('Perplexity API error')) {
|
|
83
|
+
throw new Error(`[MindHeal] ${error.message}`);
|
|
84
|
+
}
|
|
85
|
+
throw new Error(`[MindHeal] Perplexity API request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
86
|
+
}
|
|
87
|
+
const successResp = responseData;
|
|
88
|
+
const choice = successResp.choices[0];
|
|
89
|
+
if (!choice?.message?.content) {
|
|
90
|
+
throw new Error('[MindHeal] Perplexity API returned empty response');
|
|
91
|
+
}
|
|
92
|
+
const duration = Date.now() - startTime;
|
|
93
|
+
logger.debug('Perplexity API: received response', {
|
|
94
|
+
model: successResp.model,
|
|
95
|
+
duration: `${duration}ms`,
|
|
96
|
+
promptTokens: successResp.usage.prompt_tokens,
|
|
97
|
+
completionTokens: successResp.usage.completion_tokens,
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
return parseHealingResponse(choice.message.content);
|
|
101
|
+
}
|
|
102
|
+
catch (parseError) {
|
|
103
|
+
logger.error('Perplexity API: failed to parse response', {
|
|
104
|
+
rawText: choice.message.content.slice(0, 500),
|
|
105
|
+
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
106
|
+
});
|
|
107
|
+
throw parseError;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function sleep(ms) {
|
|
112
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { PerplexityProvider };
|
|
116
|
+
//# sourceMappingURL=perplexity-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perplexity-provider.js","sources":["../../../../src/ai/perplexity-provider.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|