@alvinveroy/codecompass
Version:
AI-powered MCP server for codebase navigation and LLM prompt optimization
288 lines (287 loc) • 14.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkDeepSeekApiKey = checkDeepSeekApiKey;
exports.testDeepSeekConnection = testDeepSeekConnection;
exports.generateWithDeepSeek = generateWithDeepSeek;
exports.generateEmbeddingWithDeepSeek = generateEmbeddingWithDeepSeek;
const axios_1 = __importDefault(require("axios"));
const config_service_1 = require("./config-service");
const text_utils_1 = require("../utils/text-utils");
const retry_utils_1 = require("../utils/retry-utils");
// Rate limiting state
const requestTimestamps = [];
async function waitForRateLimit() {
const now = Date.now();
const rpmLimit = config_service_1.configService.DEEPSEEK_RPM_LIMIT;
while (requestTimestamps.length > 0 && requestTimestamps[0] < now - 60000) {
requestTimestamps.shift();
}
if (requestTimestamps.length >= rpmLimit) {
const timeToWait = (requestTimestamps[0] + 60000) - now;
if (timeToWait > 0) {
config_service_1.logger.info(`DeepSeek rate limit nearly reached (${requestTimestamps.length}/${rpmLimit} requests in last minute). Delaying next request for ${timeToWait}ms.`);
await new Promise(resolve => setTimeout(resolve, timeToWait));
}
}
requestTimestamps.push(now);
}
/**
* Check if DeepSeek API key is configured
* @returns boolean - True if API key is configured, false otherwise
*/
function checkDeepSeekApiKey() {
// ConfigService handles loading the API key from file and environment.
// It also updates process.env.DEEPSEEK_API_KEY.
// This function now primarily serves to check if the key (from any source) is valid/present.
const apiKey = config_service_1.configService.DEEPSEEK_API_KEY;
if (!apiKey) {
config_service_1.logger.error("DeepSeek API key is not configured. Set DEEPSEEK_API_KEY environment variable or run 'npm run set-deepseek-key'.");
return false;
}
// process.env.DEEPSEEK_API_KEY is updated by configService.
// Global scope setting is not strictly necessary if configService is the source of truth.
config_service_1.logger.info(`DeepSeek API key configured (via ConfigService). Length: ${apiKey.length}`);
return true;
}
/**
* Test DeepSeek API connection
* @returns Promise<boolean> - True if connection is successful, false otherwise
*/
async function testDeepSeekConnection() {
try {
const apiKey = config_service_1.configService.DEEPSEEK_API_KEY;
if (!apiKey) {
config_service_1.logger.error("DeepSeek API key is not configured (via ConfigService). Set DEEPSEEK_API_KEY environment variable or use ~/.codecompass/deepseek-config.json.");
return false;
}
// process.env.DEEPSEEK_API_KEY is managed by configService.
config_service_1.logger.info(`Testing DeepSeek API connection with key length: ${apiKey.length}, key prefix: ${apiKey.substring(0, 5)}...`);
const apiUrl = config_service_1.configService.DEEPSEEK_API_URL;
// process.env.DEEPSEEK_API_URL is managed by configService.
config_service_1.logger.info(`Using DeepSeek API URL: ${apiUrl}`);
try {
const modelToTest = config_service_1.configService.DEEPSEEK_MODEL;
config_service_1.logger.info(`Sending test request to DeepSeek API at ${apiUrl}`);
config_service_1.logger.info(`Using model: ${modelToTest}`);
config_service_1.logger.debug(`DeepSeek test request payload: ${JSON.stringify({
model: modelToTest,
messages: [{ role: "user", content: "Hello" }],
max_tokens: 10
})}`);
const response = await axios_1.default.post(apiUrl, {
model: modelToTest,
messages: [{ role: "user", content: "Hello" }],
max_tokens: 10
}, {
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`
},
timeout: 15000 // Specific timeout for this test is fine
});
let dataForLogging;
try {
// response.data is 'any' from Axios. Stringify it safely for logging.
dataForLogging = JSON.stringify(response.data);
}
catch {
dataForLogging = "[Unserializable data in response]";
}
config_service_1.logger.debug(`DeepSeek API response: ${JSON.stringify({
status: response.status,
statusText: response.statusText,
headers: response.headers,
data: dataForLogging
})}`);
config_service_1.logger.info(`DeepSeek API test request to ${apiUrl} completed with status: ${response.status}`);
if (response.status === 200) {
config_service_1.logger.info("DeepSeek API connection successful");
return true;
}
config_service_1.logger.warn(`DeepSeek API test failed with status: ${response.status}`);
return false;
}
catch (requestError) {
const err = requestError instanceof Error ? requestError : new Error(String(requestError));
const logPayload = { message: err.message };
if (axios_1.default.isAxiosError(requestError)) {
logPayload.code = requestError.code;
logPayload.request = requestError.request ? 'Request present' : 'No request data';
if (requestError.response) {
let responseDataString;
try {
responseDataString = typeof requestError.response.data === 'string'
? requestError.response.data
: JSON.stringify(requestError.response.data);
}
catch {
responseDataString = "[Unserializable response data]";
}
logPayload.response = {
status: requestError.response.status,
statusText: requestError.response.statusText,
data: responseDataString,
};
}
else {
logPayload.response = 'No response data';
}
// Check for specific error types using the narrowed requestError
if (requestError.code === 'ECONNREFUSED') {
config_service_1.logger.error("Connection refused. Check if the DeepSeek API endpoint is correct and accessible.");
}
else if (requestError.response && requestError.response.status === 401) {
config_service_1.logger.error("Authentication failed. Check your DeepSeek API key.");
}
else if (requestError.response && requestError.response.status === 404) {
config_service_1.logger.error("API endpoint not found. Check the DeepSeek API URL.");
}
}
else {
// For non-Axios errors
logPayload.response = 'No response data (not an Axios error)';
logPayload.request = 'No request data (not an Axios error)';
}
config_service_1.logger.error("DeepSeek API connection test failed", logPayload);
return false;
}
}
catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
let errorCode;
let errorResponseData;
let errorResponseStatus;
if (axios_1.default.isAxiosError(error)) {
errorCode = error.code;
if (error.response) {
errorResponseData = error.response.data;
errorResponseStatus = error.response.status;
}
}
config_service_1.logger.error("DeepSeek API connection test failed (outer catch)", {
message: err.message,
code: errorCode,
response: errorResponseStatus !== undefined ? { status: errorResponseStatus, data: errorResponseData } : null,
});
return false;
}
}
/**
* Generate text with DeepSeek API
* @param prompt - The prompt to generate text from
* @returns Promise<string> - The generated text
*/
async function generateWithDeepSeek(prompt) {
try {
const apiKey = config_service_1.configService.DEEPSEEK_API_KEY;
if (!apiKey) {
config_service_1.logger.error("DeepSeek API key not configured (via ConfigService).");
throw new Error("DeepSeek API key not configured");
}
config_service_1.logger.info(`DeepSeek API key is configured with length: ${apiKey.length}`);
await waitForRateLimit();
config_service_1.logger.info(`Generating with DeepSeek for prompt (length: ${prompt.length})`);
const apiUrl = config_service_1.configService.DEEPSEEK_API_URL;
const model = config_service_1.configService.DEEPSEEK_MODEL;
config_service_1.logger.debug(`Using DeepSeek API URL: ${apiUrl}`);
config_service_1.logger.debug(`Using model: ${model}`);
const response = await (0, retry_utils_1.withRetry)(async () => {
config_service_1.logger.debug(`Sending request to DeepSeek API at ${apiUrl} with model ${model}`);
config_service_1.logger.debug(`Request payload: ${JSON.stringify({
model: model,
messages: [{ role: "user", content: `${prompt.substring(0, 50)}...` }],
temperature: 0.7,
max_tokens: 2048
})}`);
const res = await axios_1.default.post(apiUrl, {
model: model,
messages: [{ role: "user", content: prompt }],
temperature: 0.7,
max_tokens: 2048
}, {
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`
},
timeout: config_service_1.configService.REQUEST_TIMEOUT * 2
});
if (!res.data.choices || res.data.choices.length === 0) {
config_service_1.logger.error(`DeepSeek API request to ${apiUrl} failed with status ${res.status}: Invalid response structure. Response data: ${JSON.stringify(res.data)}`);
throw new Error("Invalid response from DeepSeek API");
}
config_service_1.logger.info(`DeepSeek API request to ${apiUrl} (generateText) completed with status: ${res.status}`);
return res.data.choices[0].message.content;
});
return response;
}
catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
const axiosError = error;
config_service_1.logger.error("DeepSeek API error", {
message: err.message,
code: axiosError.code,
response: axiosError.response ? {
status: axiosError.response.status,
data: axiosError.response.data
} : null,
promptLength: prompt.length,
promptSnippet: prompt.slice(0, 100) + (prompt.length > 100 ? '...' : '')
});
throw new Error(`Failed to generate with DeepSeek: ${err.message}`);
}
}
async function generateEmbeddingWithDeepSeek(text) {
try {
const apiKey = config_service_1.configService.DEEPSEEK_API_KEY;
if (!apiKey) {
config_service_1.logger.error("DeepSeek API key not configured (via ConfigService).");
throw new Error("DeepSeek API key not configured");
}
config_service_1.logger.info(`DeepSeek API key is configured with length: ${apiKey.length}`);
const processedText = (0, text_utils_1.preprocessText)(text);
await waitForRateLimit();
const embeddingUrl = config_service_1.configService.DEEPSEEK_API_URL.includes("api.deepseek.com")
? "https://api.deepseek.com/embeddings"
: config_service_1.configService.DEEPSEEK_API_URL.replace("/chat/completions", "/embeddings");
config_service_1.logger.info(`Generating embedding with DeepSeek for text (length: ${processedText.length})`);
config_service_1.logger.debug(`Using DeepSeek embedding URL: ${embeddingUrl}`);
const response = await (0, retry_utils_1.withRetry)(async () => {
config_service_1.logger.debug(`Sending embedding request to DeepSeek API with model: deepseek-embedding`);
const res = await axios_1.default.post(embeddingUrl, {
model: "deepseek-embedding",
input: processedText
}, {
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${apiKey}`
},
timeout: config_service_1.configService.REQUEST_TIMEOUT * 1.5
});
if (!res.data.data || !res.data.data[0].embedding) {
config_service_1.logger.error(`DeepSeek API request to ${embeddingUrl} failed with status ${res.status}: Invalid embedding response structure. Response data: ${JSON.stringify(res.data)}`);
throw new Error("Invalid embedding response from DeepSeek API");
}
config_service_1.logger.info(`DeepSeek API request to ${embeddingUrl} (generateEmbedding) completed with status: ${res.status}`);
return res.data.data[0].embedding;
});
return response;
}
catch (error) {
const err = error instanceof Error ? error : new Error(String(error));
const axiosError = error;
config_service_1.logger.error("DeepSeek embedding error", {
message: err.message,
code: axiosError.code,
response: axiosError.response ? {
status: axiosError.response.status,
data: axiosError.response.data
} : null,
textLength: text.length,
textSnippet: text.slice(0, 100) + (text.length > 100 ? '...' : '')
});
throw new Error(`Failed to generate embedding with DeepSeek: ${err.message}`);
}
}