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