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,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var promptTemplates = require('./prompt-templates.js');
|
|
4
|
+
var logger = require('../utils/logger.js');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_MODEL = 'claude-sonnet-4-20250514';
|
|
7
|
+
const DEFAULT_MAX_TOKENS = 1024;
|
|
8
|
+
const DEFAULT_TEMPERATURE = 0;
|
|
9
|
+
const API_URL = 'https://api.anthropic.com/v1/messages';
|
|
10
|
+
const ANTHROPIC_VERSION = '2023-06-01';
|
|
11
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
12
|
+
class AnthropicProvider {
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.name = 'anthropic';
|
|
15
|
+
if (!config.apiKey) {
|
|
16
|
+
throw new Error('[MindHeal] Anthropic API key is required');
|
|
17
|
+
}
|
|
18
|
+
this.apiKey = config.apiKey;
|
|
19
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
20
|
+
this.maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
21
|
+
this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;
|
|
22
|
+
}
|
|
23
|
+
async suggestLocator(request) {
|
|
24
|
+
const prompt = promptTemplates.buildHealingPrompt(request);
|
|
25
|
+
const startTime = Date.now();
|
|
26
|
+
logger.logger.debug('Anthropic API: sending healing request', {
|
|
27
|
+
model: this.model,
|
|
28
|
+
originalLocator: request.originalLocator.selector,
|
|
29
|
+
pageUrl: request.pageUrl,
|
|
30
|
+
});
|
|
31
|
+
const messages = [
|
|
32
|
+
{ role: 'user', content: prompt },
|
|
33
|
+
];
|
|
34
|
+
const body = JSON.stringify({
|
|
35
|
+
model: this.model,
|
|
36
|
+
max_tokens: this.maxTokens,
|
|
37
|
+
temperature: this.temperature,
|
|
38
|
+
messages,
|
|
39
|
+
});
|
|
40
|
+
let responseData;
|
|
41
|
+
try {
|
|
42
|
+
const controller = new AbortController();
|
|
43
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
44
|
+
const response = await fetch(API_URL, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: {
|
|
47
|
+
'content-type': 'application/json',
|
|
48
|
+
'x-api-key': this.apiKey,
|
|
49
|
+
'anthropic-version': ANTHROPIC_VERSION,
|
|
50
|
+
},
|
|
51
|
+
body,
|
|
52
|
+
signal: controller.signal,
|
|
53
|
+
});
|
|
54
|
+
clearTimeout(timeoutId);
|
|
55
|
+
if (response.status === 429) {
|
|
56
|
+
const retryAfter = response.headers.get('retry-after');
|
|
57
|
+
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 5;
|
|
58
|
+
logger.logger.warn(`Anthropic API: rate limited, retrying after ${waitSeconds}s`);
|
|
59
|
+
await sleep(waitSeconds * 1000);
|
|
60
|
+
return this.suggestLocator(request);
|
|
61
|
+
}
|
|
62
|
+
responseData = (await response.json());
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const errorResp = responseData;
|
|
65
|
+
throw new Error(`Anthropic API error (${response.status}): ${errorResp.error?.message ?? response.statusText}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
70
|
+
throw new Error(`[MindHeal] Anthropic API request timed out after ${REQUEST_TIMEOUT_MS}ms`);
|
|
71
|
+
}
|
|
72
|
+
if (error instanceof Error && error.message.startsWith('Anthropic API error')) {
|
|
73
|
+
throw new Error(`[MindHeal] ${error.message}`);
|
|
74
|
+
}
|
|
75
|
+
throw new Error(`[MindHeal] Anthropic API request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
76
|
+
}
|
|
77
|
+
const successResp = responseData;
|
|
78
|
+
const textContent = successResp.content.find((c) => c.type === 'text');
|
|
79
|
+
if (!textContent?.text) {
|
|
80
|
+
throw new Error('[MindHeal] Anthropic API returned empty response');
|
|
81
|
+
}
|
|
82
|
+
const duration = Date.now() - startTime;
|
|
83
|
+
logger.logger.debug('Anthropic API: received response', {
|
|
84
|
+
model: successResp.model,
|
|
85
|
+
duration: `${duration}ms`,
|
|
86
|
+
inputTokens: successResp.usage.input_tokens,
|
|
87
|
+
outputTokens: successResp.usage.output_tokens,
|
|
88
|
+
});
|
|
89
|
+
try {
|
|
90
|
+
return promptTemplates.parseHealingResponse(textContent.text);
|
|
91
|
+
}
|
|
92
|
+
catch (parseError) {
|
|
93
|
+
logger.logger.error('Anthropic API: failed to parse response', {
|
|
94
|
+
rawText: textContent.text.slice(0, 500),
|
|
95
|
+
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
96
|
+
});
|
|
97
|
+
throw parseError;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function sleep(ms) {
|
|
102
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
exports.AnthropicProvider = AnthropicProvider;
|
|
106
|
+
//# sourceMappingURL=anthropic-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anthropic-provider.js","sources":["../../../../src/ai/anthropic-provider.ts"],"sourcesContent":[null],"names":["buildHealingPrompt","logger","parseHealingResponse"],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var promptTemplates = require('./prompt-templates.js');
|
|
4
|
+
var logger = require('../utils/logger.js');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_API_VERSION = '2024-02-01';
|
|
7
|
+
const DEFAULT_MAX_TOKENS = 1024;
|
|
8
|
+
const DEFAULT_TEMPERATURE = 0;
|
|
9
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
10
|
+
class AzureOpenAIProvider {
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.name = 'azure-openai';
|
|
13
|
+
if (!config.apiKey) {
|
|
14
|
+
throw new Error('[MindHeal] Azure OpenAI API key is required');
|
|
15
|
+
}
|
|
16
|
+
if (!config.baseUrl) {
|
|
17
|
+
throw new Error('[MindHeal] Azure OpenAI baseUrl is required (e.g., https://my-resource.openai.azure.com)');
|
|
18
|
+
}
|
|
19
|
+
if (!config.azureDeploymentName) {
|
|
20
|
+
throw new Error('[MindHeal] Azure OpenAI deployment name is required');
|
|
21
|
+
}
|
|
22
|
+
this.apiKey = config.apiKey;
|
|
23
|
+
this.deploymentName = config.azureDeploymentName;
|
|
24
|
+
this.apiVersion = config.azureApiVersion ?? DEFAULT_API_VERSION;
|
|
25
|
+
this.maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
26
|
+
this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;
|
|
27
|
+
// Strip trailing slash for consistency
|
|
28
|
+
let baseUrl = config.baseUrl;
|
|
29
|
+
if (baseUrl.endsWith('/')) {
|
|
30
|
+
baseUrl = baseUrl.slice(0, -1);
|
|
31
|
+
}
|
|
32
|
+
this.baseUrl = baseUrl;
|
|
33
|
+
}
|
|
34
|
+
async suggestLocator(request) {
|
|
35
|
+
const fullPrompt = promptTemplates.buildHealingPrompt(request);
|
|
36
|
+
const startTime = Date.now();
|
|
37
|
+
logger.logger.debug('Azure OpenAI API: sending healing request', {
|
|
38
|
+
deploymentName: this.deploymentName,
|
|
39
|
+
apiVersion: this.apiVersion,
|
|
40
|
+
originalLocator: request.originalLocator.selector,
|
|
41
|
+
pageUrl: request.pageUrl,
|
|
42
|
+
});
|
|
43
|
+
// Split the combined prompt into system + user messages at the separator
|
|
44
|
+
const separatorIndex = fullPrompt.indexOf('\n\n---\n\n');
|
|
45
|
+
let systemContent;
|
|
46
|
+
let userContent;
|
|
47
|
+
if (separatorIndex !== -1) {
|
|
48
|
+
systemContent = fullPrompt.slice(0, separatorIndex);
|
|
49
|
+
userContent = fullPrompt.slice(separatorIndex + 7); // length of '\n\n---\n\n'
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
systemContent = 'You are an expert Playwright test engineer. Respond with only valid JSON.';
|
|
53
|
+
userContent = fullPrompt;
|
|
54
|
+
}
|
|
55
|
+
const messages = [
|
|
56
|
+
{ role: 'system', content: systemContent },
|
|
57
|
+
{ role: 'user', content: userContent },
|
|
58
|
+
];
|
|
59
|
+
const body = JSON.stringify({
|
|
60
|
+
max_tokens: this.maxTokens,
|
|
61
|
+
temperature: this.temperature,
|
|
62
|
+
messages,
|
|
63
|
+
});
|
|
64
|
+
const url = `${this.baseUrl}/openai/deployments/${this.deploymentName}/chat/completions?api-version=${this.apiVersion}`;
|
|
65
|
+
let responseData;
|
|
66
|
+
try {
|
|
67
|
+
const controller = new AbortController();
|
|
68
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
69
|
+
const response = await fetch(url, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {
|
|
72
|
+
'content-type': 'application/json',
|
|
73
|
+
'api-key': this.apiKey,
|
|
74
|
+
},
|
|
75
|
+
body,
|
|
76
|
+
signal: controller.signal,
|
|
77
|
+
});
|
|
78
|
+
clearTimeout(timeoutId);
|
|
79
|
+
if (response.status === 429) {
|
|
80
|
+
const retryAfter = response.headers.get('retry-after');
|
|
81
|
+
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 5;
|
|
82
|
+
logger.logger.warn(`Azure OpenAI API: rate limited, retrying after ${waitSeconds}s`);
|
|
83
|
+
await sleep(waitSeconds * 1000);
|
|
84
|
+
return this.suggestLocator(request);
|
|
85
|
+
}
|
|
86
|
+
responseData = (await response.json());
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
const errorResp = responseData;
|
|
89
|
+
throw new Error(`Azure OpenAI API error (${response.status}): ${errorResp.error?.message ?? response.statusText}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
94
|
+
throw new Error(`[MindHeal] Azure OpenAI API request timed out after ${REQUEST_TIMEOUT_MS}ms`);
|
|
95
|
+
}
|
|
96
|
+
if (error instanceof Error && error.message.startsWith('Azure OpenAI API error')) {
|
|
97
|
+
throw new Error(`[MindHeal] ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`[MindHeal] Azure OpenAI API request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
100
|
+
}
|
|
101
|
+
const successResp = responseData;
|
|
102
|
+
const choice = successResp.choices[0];
|
|
103
|
+
if (!choice?.message?.content) {
|
|
104
|
+
throw new Error('[MindHeal] Azure OpenAI API returned empty response');
|
|
105
|
+
}
|
|
106
|
+
const duration = Date.now() - startTime;
|
|
107
|
+
logger.logger.debug('Azure OpenAI API: received response', {
|
|
108
|
+
model: successResp.model,
|
|
109
|
+
duration: `${duration}ms`,
|
|
110
|
+
promptTokens: successResp.usage.prompt_tokens,
|
|
111
|
+
completionTokens: successResp.usage.completion_tokens,
|
|
112
|
+
});
|
|
113
|
+
try {
|
|
114
|
+
return promptTemplates.parseHealingResponse(choice.message.content);
|
|
115
|
+
}
|
|
116
|
+
catch (parseError) {
|
|
117
|
+
logger.logger.error('Azure OpenAI API: failed to parse response', {
|
|
118
|
+
rawText: choice.message.content.slice(0, 500),
|
|
119
|
+
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
120
|
+
});
|
|
121
|
+
throw parseError;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function sleep(ms) {
|
|
126
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
exports.AzureOpenAIProvider = AzureOpenAIProvider;
|
|
130
|
+
//# sourceMappingURL=azure-openai-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"azure-openai-provider.js","sources":["../../../../src/ai/azure-openai-provider.ts"],"sourcesContent":[null],"names":["buildHealingPrompt","logger","parseHealingResponse"],"mappings":"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"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var promptTemplates = require('./prompt-templates.js');
|
|
4
|
+
var logger = require('../utils/logger.js');
|
|
5
|
+
var crypto = require('crypto');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_MODEL = 'anthropic.claude-3-haiku-20240307-v1:0';
|
|
8
|
+
const DEFAULT_MAX_TOKENS = 1024;
|
|
9
|
+
const DEFAULT_TEMPERATURE = 0.1;
|
|
10
|
+
const DEFAULT_REGION = 'us-east-1';
|
|
11
|
+
const BEDROCK_SERVICE = 'bedrock-runtime';
|
|
12
|
+
const ANTHROPIC_BEDROCK_VERSION = 'bedrock-2023-05-31';
|
|
13
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
14
|
+
// ─── Minimal AWS Signature V4 Implementation ──────────────────────────────────
|
|
15
|
+
function sha256(data) {
|
|
16
|
+
return crypto.createHash('sha256').update(data).digest('hex');
|
|
17
|
+
}
|
|
18
|
+
function hmacSha256(key, data) {
|
|
19
|
+
return crypto.createHmac('sha256', key).update(data).digest();
|
|
20
|
+
}
|
|
21
|
+
function getSigningKey(secretKey, dateStamp, region, service) {
|
|
22
|
+
const kDate = hmacSha256(`AWS4${secretKey}`, dateStamp);
|
|
23
|
+
const kRegion = hmacSha256(kDate, region);
|
|
24
|
+
const kService = hmacSha256(kRegion, service);
|
|
25
|
+
const kSigning = hmacSha256(kService, 'aws4_request');
|
|
26
|
+
return kSigning;
|
|
27
|
+
}
|
|
28
|
+
function buildAwsV4Headers(params) {
|
|
29
|
+
const { method, host, path, body, region, service, accessKeyId, secretAccessKey, sessionToken } = params;
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const amzDate = now.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
|
|
32
|
+
const dateStamp = amzDate.slice(0, 8);
|
|
33
|
+
const payloadHash = sha256(body);
|
|
34
|
+
const headers = {
|
|
35
|
+
'content-type': 'application/json',
|
|
36
|
+
'host': host,
|
|
37
|
+
'x-amz-date': amzDate,
|
|
38
|
+
'x-amz-content-sha256': payloadHash,
|
|
39
|
+
};
|
|
40
|
+
if (sessionToken) {
|
|
41
|
+
headers['x-amz-security-token'] = sessionToken;
|
|
42
|
+
}
|
|
43
|
+
// Canonical headers — must be sorted by lowercase header name
|
|
44
|
+
const signedHeaderKeys = Object.keys(headers).sort();
|
|
45
|
+
const canonicalHeaders = signedHeaderKeys.map((k) => `${k}:${headers[k]}\n`).join('');
|
|
46
|
+
const signedHeaders = signedHeaderKeys.join(';');
|
|
47
|
+
// Canonical request
|
|
48
|
+
const canonicalRequest = [
|
|
49
|
+
method,
|
|
50
|
+
path,
|
|
51
|
+
'', // query string (empty)
|
|
52
|
+
canonicalHeaders,
|
|
53
|
+
signedHeaders,
|
|
54
|
+
payloadHash,
|
|
55
|
+
].join('\n');
|
|
56
|
+
// String to sign
|
|
57
|
+
const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
|
|
58
|
+
const stringToSign = ['AWS4-HMAC-SHA256', amzDate, credentialScope, sha256(canonicalRequest)].join('\n');
|
|
59
|
+
// Signing key and signature
|
|
60
|
+
const signingKey = getSigningKey(secretAccessKey, dateStamp, region, service);
|
|
61
|
+
const signature = crypto.createHmac('sha256', signingKey).update(stringToSign).digest('hex');
|
|
62
|
+
// Authorization header
|
|
63
|
+
const authorization = `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
64
|
+
return {
|
|
65
|
+
...headers,
|
|
66
|
+
'authorization': authorization,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// ─── Bedrock Provider ─────────────────────────────────────────────────────────
|
|
70
|
+
class BedrockProvider {
|
|
71
|
+
constructor(config) {
|
|
72
|
+
this.name = 'aws-bedrock';
|
|
73
|
+
if (!config.awsAccessKeyId) {
|
|
74
|
+
throw new Error('[MindHeal] AWS access key ID is required for Bedrock provider');
|
|
75
|
+
}
|
|
76
|
+
if (!config.awsSecretAccessKey) {
|
|
77
|
+
throw new Error('[MindHeal] AWS secret access key is required for Bedrock provider');
|
|
78
|
+
}
|
|
79
|
+
this.region = config.awsRegion ?? DEFAULT_REGION;
|
|
80
|
+
this.accessKeyId = config.awsAccessKeyId;
|
|
81
|
+
this.secretAccessKey = config.awsSecretAccessKey;
|
|
82
|
+
this.sessionToken = config.awsSessionToken;
|
|
83
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
84
|
+
this.maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
85
|
+
this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;
|
|
86
|
+
}
|
|
87
|
+
async suggestLocator(request) {
|
|
88
|
+
const fullPrompt = promptTemplates.buildHealingPrompt(request);
|
|
89
|
+
const startTime = Date.now();
|
|
90
|
+
logger.logger.debug('Bedrock API: sending healing request', {
|
|
91
|
+
model: this.model,
|
|
92
|
+
region: this.region,
|
|
93
|
+
originalLocator: request.originalLocator.selector,
|
|
94
|
+
pageUrl: request.pageUrl,
|
|
95
|
+
});
|
|
96
|
+
// Bedrock Anthropic models accept a single user message with the combined prompt.
|
|
97
|
+
// We do not split into system/user here because the Bedrock Anthropic invoke API
|
|
98
|
+
// uses the messages format where system is a top-level field, and keeping it
|
|
99
|
+
// simple with one user message is the most portable approach.
|
|
100
|
+
const requestBody = JSON.stringify({
|
|
101
|
+
anthropic_version: ANTHROPIC_BEDROCK_VERSION,
|
|
102
|
+
max_tokens: this.maxTokens,
|
|
103
|
+
temperature: this.temperature,
|
|
104
|
+
messages: [{ role: 'user', content: fullPrompt }],
|
|
105
|
+
});
|
|
106
|
+
const host = `${BEDROCK_SERVICE}.${this.region}.amazonaws.com`;
|
|
107
|
+
const path = `/model/${encodeURIComponent(this.model)}/invoke`;
|
|
108
|
+
const url = `https://${host}${path}`;
|
|
109
|
+
const headers = buildAwsV4Headers({
|
|
110
|
+
method: 'POST',
|
|
111
|
+
host,
|
|
112
|
+
path,
|
|
113
|
+
body: requestBody,
|
|
114
|
+
region: this.region,
|
|
115
|
+
service: BEDROCK_SERVICE,
|
|
116
|
+
accessKeyId: this.accessKeyId,
|
|
117
|
+
secretAccessKey: this.secretAccessKey,
|
|
118
|
+
sessionToken: this.sessionToken,
|
|
119
|
+
});
|
|
120
|
+
let responseData;
|
|
121
|
+
try {
|
|
122
|
+
const controller = new AbortController();
|
|
123
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
124
|
+
const response = await fetch(url, {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers,
|
|
127
|
+
body: requestBody,
|
|
128
|
+
signal: controller.signal,
|
|
129
|
+
});
|
|
130
|
+
clearTimeout(timeoutId);
|
|
131
|
+
if (response.status === 429) {
|
|
132
|
+
const retryAfter = response.headers.get('retry-after');
|
|
133
|
+
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 5;
|
|
134
|
+
logger.logger.warn(`Bedrock API: rate limited, retrying after ${waitSeconds}s`);
|
|
135
|
+
await sleep(waitSeconds * 1000);
|
|
136
|
+
return this.suggestLocator(request);
|
|
137
|
+
}
|
|
138
|
+
responseData = (await response.json());
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
const errorResp = responseData;
|
|
141
|
+
const errorMessage = errorResp.message ?? errorResp.Message ?? response.statusText;
|
|
142
|
+
throw new Error(`Bedrock API error (${response.status}): ${errorMessage}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
147
|
+
throw new Error(`[MindHeal] Bedrock API request timed out after ${REQUEST_TIMEOUT_MS}ms`);
|
|
148
|
+
}
|
|
149
|
+
if (error instanceof Error && error.message.startsWith('Bedrock API error')) {
|
|
150
|
+
throw new Error(`[MindHeal] ${error.message}`);
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`[MindHeal] Bedrock API request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
153
|
+
}
|
|
154
|
+
const successResp = responseData;
|
|
155
|
+
const textContent = successResp.content?.find((c) => c.type === 'text');
|
|
156
|
+
if (!textContent?.text) {
|
|
157
|
+
throw new Error('[MindHeal] Bedrock API returned empty response');
|
|
158
|
+
}
|
|
159
|
+
const duration = Date.now() - startTime;
|
|
160
|
+
logger.logger.debug('Bedrock API: received response', {
|
|
161
|
+
model: this.model,
|
|
162
|
+
duration: `${duration}ms`,
|
|
163
|
+
inputTokens: successResp.usage?.input_tokens,
|
|
164
|
+
outputTokens: successResp.usage?.output_tokens,
|
|
165
|
+
});
|
|
166
|
+
try {
|
|
167
|
+
return promptTemplates.parseHealingResponse(textContent.text);
|
|
168
|
+
}
|
|
169
|
+
catch (parseError) {
|
|
170
|
+
logger.logger.error('Bedrock API: failed to parse response', {
|
|
171
|
+
rawText: textContent.text.slice(0, 500),
|
|
172
|
+
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
173
|
+
});
|
|
174
|
+
throw parseError;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function sleep(ms) {
|
|
179
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
exports.BedrockProvider = BedrockProvider;
|
|
183
|
+
//# sourceMappingURL=bedrock-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bedrock-provider.js","sources":["../../../../src/ai/bedrock-provider.ts"],"sourcesContent":[null],"names":["createHash","createHmac","buildHealingPrompt","logger","parseHealingResponse"],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var promptTemplates = require('./prompt-templates.js');
|
|
4
|
+
var logger = require('../utils/logger.js');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_MODEL = 'deepseek-chat';
|
|
7
|
+
const DEFAULT_MAX_TOKENS = 1024;
|
|
8
|
+
const DEFAULT_TEMPERATURE = 0;
|
|
9
|
+
const DEFAULT_API_URL = 'https://api.deepseek.com/chat/completions';
|
|
10
|
+
const REQUEST_TIMEOUT_MS = 30000;
|
|
11
|
+
class DeepSeekProvider {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
this.name = 'deepseek';
|
|
14
|
+
if (!config.apiKey) {
|
|
15
|
+
throw new Error('[MindHeal] DeepSeek API key is required');
|
|
16
|
+
}
|
|
17
|
+
this.apiKey = config.apiKey;
|
|
18
|
+
this.model = config.model ?? DEFAULT_MODEL;
|
|
19
|
+
this.maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
|
|
20
|
+
this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;
|
|
21
|
+
this.apiUrl = config.baseUrl ?? DEFAULT_API_URL;
|
|
22
|
+
}
|
|
23
|
+
async suggestLocator(request) {
|
|
24
|
+
const fullPrompt = promptTemplates.buildHealingPrompt(request);
|
|
25
|
+
const startTime = Date.now();
|
|
26
|
+
logger.logger.debug('DeepSeek API: sending healing request', {
|
|
27
|
+
model: this.model,
|
|
28
|
+
originalLocator: request.originalLocator.selector,
|
|
29
|
+
pageUrl: request.pageUrl,
|
|
30
|
+
});
|
|
31
|
+
// Split the combined prompt into system + user messages at the separator
|
|
32
|
+
const separatorIndex = fullPrompt.indexOf('\n\n---\n\n');
|
|
33
|
+
let systemContent;
|
|
34
|
+
let userContent;
|
|
35
|
+
if (separatorIndex !== -1) {
|
|
36
|
+
systemContent = fullPrompt.slice(0, separatorIndex);
|
|
37
|
+
userContent = fullPrompt.slice(separatorIndex + 7); // length of '\n\n---\n\n'
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
systemContent = 'You are an expert Playwright test engineer. Respond with only valid JSON.';
|
|
41
|
+
userContent = fullPrompt;
|
|
42
|
+
}
|
|
43
|
+
const messages = [
|
|
44
|
+
{ role: 'system', content: systemContent },
|
|
45
|
+
{ role: 'user', content: userContent },
|
|
46
|
+
];
|
|
47
|
+
const body = JSON.stringify({
|
|
48
|
+
model: this.model,
|
|
49
|
+
max_tokens: this.maxTokens,
|
|
50
|
+
temperature: this.temperature,
|
|
51
|
+
messages,
|
|
52
|
+
});
|
|
53
|
+
let responseData;
|
|
54
|
+
try {
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
57
|
+
const response = await fetch(this.apiUrl, {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: {
|
|
60
|
+
'content-type': 'application/json',
|
|
61
|
+
'authorization': `Bearer ${this.apiKey}`,
|
|
62
|
+
},
|
|
63
|
+
body,
|
|
64
|
+
signal: controller.signal,
|
|
65
|
+
});
|
|
66
|
+
clearTimeout(timeoutId);
|
|
67
|
+
if (response.status === 429) {
|
|
68
|
+
const retryAfter = response.headers.get('retry-after');
|
|
69
|
+
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 5;
|
|
70
|
+
logger.logger.warn(`DeepSeek API: rate limited, retrying after ${waitSeconds}s`);
|
|
71
|
+
await sleep(waitSeconds * 1000);
|
|
72
|
+
return this.suggestLocator(request);
|
|
73
|
+
}
|
|
74
|
+
responseData = (await response.json());
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const errorResp = responseData;
|
|
77
|
+
throw new Error(`DeepSeek API error (${response.status}): ${errorResp.error?.message ?? response.statusText}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
82
|
+
throw new Error(`[MindHeal] DeepSeek API request timed out after ${REQUEST_TIMEOUT_MS}ms`);
|
|
83
|
+
}
|
|
84
|
+
if (error instanceof Error && error.message.startsWith('DeepSeek API error')) {
|
|
85
|
+
throw new Error(`[MindHeal] ${error.message}`);
|
|
86
|
+
}
|
|
87
|
+
throw new Error(`[MindHeal] DeepSeek API request failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
88
|
+
}
|
|
89
|
+
const successResp = responseData;
|
|
90
|
+
const choice = successResp.choices[0];
|
|
91
|
+
if (!choice?.message?.content) {
|
|
92
|
+
throw new Error('[MindHeal] DeepSeek API returned empty response');
|
|
93
|
+
}
|
|
94
|
+
const duration = Date.now() - startTime;
|
|
95
|
+
logger.logger.debug('DeepSeek API: received response', {
|
|
96
|
+
model: successResp.model,
|
|
97
|
+
duration: `${duration}ms`,
|
|
98
|
+
promptTokens: successResp.usage.prompt_tokens,
|
|
99
|
+
completionTokens: successResp.usage.completion_tokens,
|
|
100
|
+
});
|
|
101
|
+
try {
|
|
102
|
+
return promptTemplates.parseHealingResponse(choice.message.content);
|
|
103
|
+
}
|
|
104
|
+
catch (parseError) {
|
|
105
|
+
logger.logger.error('DeepSeek API: failed to parse response', {
|
|
106
|
+
rawText: choice.message.content.slice(0, 500),
|
|
107
|
+
error: parseError instanceof Error ? parseError.message : String(parseError),
|
|
108
|
+
});
|
|
109
|
+
throw parseError;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function sleep(ms) {
|
|
114
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
exports.DeepSeekProvider = DeepSeekProvider;
|
|
118
|
+
//# sourceMappingURL=deepseek-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deepseek-provider.js","sources":["../../../../src/ai/deepseek-provider.ts"],"sourcesContent":[null],"names":["buildHealingPrompt","logger","parseHealingResponse"],"mappings":"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"}
|