@@ -129376,9 +129376,12 @@ class AiRepository {
129376129376 (0, logger_1.logError)('Missing required AI configuration');
129377129377 return undefined;
129378129378 }
129379+ (0, logger_1.logDebugInfo)(`🔎 Model: ${model}`);
129380+ (0, logger_1.logDebugInfo)(`🔎 API Key: ***`);
129381+ (0, logger_1.logDebugInfo)(`🔎 Provider Routing: ${JSON.stringify(providerRouting, null, 2)}`);
129379129382 const url = `https://openrouter.ai/api/v1/chat/completions`;
129380129383 try {
129381- (0, logger_1. logDebugInfo) (`Sending prompt to ${model}: ${prompt}`);
129384+ // logDebugInfo(`Sending prompt to ${model}: ${prompt}`);
129382129385 const requestBody = {
129383129386 model: model,
129384129387 messages: [
@@ -129418,6 +129421,18 @@ class AiRepository {
129418129421 return undefined;
129419129422 }
129420129423 };
129424+ this.askJson = async (ai, prompt) => {
129425+ const result = await this.ask(ai, prompt);
129426+ if (!result) {
129427+ return undefined;
129428+ }
129429+ // Clean the response by removing ```json markers if present
129430+ const cleanedResult = result
129431+ .replace(/^```json\n?/, '') // Remove ```json at the start
129432+ .replace(/\n?```$/, '') // Remove ``` at the end
129433+ .trim();
129434+ return JSON.parse(cleanedResult);
129435+ };
129421129436 }
129422129437}
129423129438exports.AiRepository = AiRepository;
@@ -130548,6 +130563,70 @@ class FileRepository {
130548130563 /^\/\*\*$/.test(trimmed) ||
130549130564 /^\*\/$/.test(trimmed));
130550130565 };
130566+ this.getFileTree = async (owner, repository, token, branch, ignoreFiles, progress) => {
130567+ const fileContents = await this.getRepositoryContent(owner, repository, token, branch, ignoreFiles, progress);
130568+ // Create root nodes for both trees
130569+ const rootWithContent = {
130570+ name: repository,
130571+ type: 'directory',
130572+ path: '',
130573+ children: []
130574+ };
130575+ const rootWithoutContent = {
130576+ name: repository,
130577+ type: 'directory',
130578+ path: '',
130579+ children: []
130580+ };
130581+ // Process each file path to build both trees
130582+ for (const [filePath, content] of fileContents.entries()) {
130583+ const parts = filePath.split('/');
130584+ let currentLevelWithContent = rootWithContent;
130585+ let currentLevelWithoutContent = rootWithoutContent;
130586+ for (let i = 0; i < parts.length; i++) {
130587+ const part = parts[i];
130588+ const isLastPart = i === parts.length - 1;
130589+ const currentPath = parts.slice(0, i + 1).join('/');
130590+ // Find or create the node in the content tree
130591+ let nodeWithContent = currentLevelWithContent.children?.find(n => n.name === part);
130592+ if (!nodeWithContent) {
130593+ nodeWithContent = {
130594+ name: part,
130595+ type: isLastPart ? 'file' : 'directory',
130596+ path: currentPath,
130597+ children: isLastPart ? undefined : [],
130598+ content: isLastPart ? content : undefined
130599+ };
130600+ if (!currentLevelWithContent.children) {
130601+ currentLevelWithContent.children = [];
130602+ }
130603+ currentLevelWithContent.children.push(nodeWithContent);
130604+ }
130605+ // Find or create the node in the no-content tree
130606+ let nodeWithoutContent = currentLevelWithoutContent.children?.find(n => n.name === part);
130607+ if (!nodeWithoutContent) {
130608+ nodeWithoutContent = {
130609+ name: part,
130610+ type: isLastPart ? 'file' : 'directory',
130611+ path: currentPath,
130612+ children: isLastPart ? undefined : []
130613+ };
130614+ if (!currentLevelWithoutContent.children) {
130615+ currentLevelWithoutContent.children = [];
130616+ }
130617+ currentLevelWithoutContent.children.push(nodeWithoutContent);
130618+ }
130619+ if (!isLastPart) {
130620+ currentLevelWithContent = nodeWithContent;
130621+ currentLevelWithoutContent = nodeWithoutContent;
130622+ }
130623+ }
130624+ }
130625+ return {
130626+ withContent: rootWithContent,
130627+ withoutContent: rootWithoutContent
130628+ };
130629+ };
130551130630 }
130552130631 isMediaOrPdfFile(path) {
130553130632 const mediaExtensions = [
@@ -132176,23 +132255,36 @@ exports.ConfigurationHandler = ConfigurationHandler;
132176132255Object.defineProperty(exports, "__esModule", ({ value: true }));
132177132256exports.AskActionUseCase = void 0;
132178132257const result_1 = __nccwpck_require__(27305);
132258+ const ai_repository_1 = __nccwpck_require__(68307);
132179132259const docker_repository_1 = __nccwpck_require__(19097);
132180132260const file_repository_1 = __nccwpck_require__(81503);
132261+ const issue_repository_1 = __nccwpck_require__(40057);
132181132262const supabase_repository_1 = __nccwpck_require__(79829);
132182132263const logger_1 = __nccwpck_require__(38836);
132183132264class AskActionUseCase {
132184132265 constructor() {
132185132266 this.taskId = 'AskActionUseCase';
132186132267 this.dockerRepository = new docker_repository_1.DockerRepository();
132187132268 this.fileRepository = new file_repository_1.FileRepository();
132188- this.CODE_INSTRUCTION_BLOCK = "Represent the code for semantic search" ;
132189- this.CODE_INSTRUCTION_LINE = "Represent each line of code for retrieval" ;
132269+ this.aiRepository = new ai_repository_1.AiRepository() ;
132270+ this.issueRepository = new issue_repository_1.IssueRepository() ;
132190132271 this.CODE_INSTRUCTION_ASK = "Represent the question for retrieving relevant code snippets";
132191132272 }
132192132273 async invoke(param) {
132193132274 (0, logger_1.logInfo)(`Executing ${this.taskId}.`);
132194132275 const results = [];
132195132276 try {
132277+ if (param.ai.getOpenRouterModel().length === 0 || param.ai.getOpenRouterApiKey().length === 0) {
132278+ results.push(new result_1.Result({
132279+ id: this.taskId,
132280+ success: false,
132281+ executed: false,
132282+ errors: [
132283+ `OpenRouter model or API key not found.`,
132284+ ],
132285+ }));
132286+ return results;
132287+ }
132196132288 /**
132197132289 * Check if the user from the token is found.
132198132290 */
@@ -132210,12 +132302,15 @@ class AskActionUseCase {
132210132302 /**
132211132303 * Get the comment body.
132212132304 */
132305+ let description = '';
132213132306 let commentBody = '';
132214132307 if (param.issue.isIssueComment) {
132215132308 commentBody = param.issue.commentBody;
132309+ description = await this.issueRepository.getDescription(param.owner, param.repo, param.issueNumber, param.tokenUser) ?? '';
132216132310 }
132217132311 else if (param.pullRequest.isPullRequestReviewComment) {
132218132312 commentBody = param.pullRequest.commentBody;
132313+ description = await this.issueRepository.getDescription(param.owner, param.repo, param.issueNumber, param.tokenUser) ?? '';
132219132314 }
132220132315 else {
132221132316 (0, logger_1.logError)(`Not a valid comment body.`);
@@ -132266,8 +132361,11 @@ class AskActionUseCase {
132266132361 const embeddings = await this.dockerRepository.getEmbedding(param, [
132267132362 [this.CODE_INSTRUCTION_ASK, commentBody]
132268132363 ]);
132269- (0, logger_1.logInfo)(`🔎 Embeddings: ${JSON.stringify(embeddings, null, 2)}`);
132270- const types = ['line', 'block'];
132364+ // logInfo(`🔎 Embeddings: ${JSON.stringify(embeddings, null, 2)}`);
132365+ const types = [
132366+ // 'line',
132367+ 'block'
132368+ ];
132271132369 const chunks = [];
132272132370 for (const type of types) {
132273132371 (0, logger_1.logInfo)(`📦 🔎 Matching chunks for ${param.owner}/${param.repo}/${param.commit.branch}`);
@@ -132277,6 +132375,74 @@ class AskActionUseCase {
132277132375 }
132278132376 chunks.push(...foundChunks);
132279132377 }
132378+ const { withContent, withoutContent } = await this.fileRepository.getFileTree(param.owner, param.repo, param.tokens.token, param.commit.branch, param.ai.getAiIgnoreFiles(), (fileName) => {
132379+ (0, logger_1.logSingleLine)(`Checking file ${fileName}`);
132380+ });
132381+ let workComplete = false;
132382+ let relatedFiles = new Map();
132383+ let finalResponse = '';
132384+ while (!workComplete) {
132385+ const prompt = `
132386+ You are a highly skilled code analysis assistant. I will provide you with:
132387+ 1. A user's question about a codebase
132388+ 2. A file tree representing the structure of the project
132389+ 3. The most relevant code snippets from the codebase related to their query
132390+
132391+ Your tasks are:
132392+ - Analyze the code snippets in the context of the user's question.
132393+ - Use the file tree to provide additional context if needed (e.g., to understand module relationships).
132394+ - Provide your answer **only** in a JSON format, following this structure:
132395+
132396+ {
132397+ "text_response": "Your detailed analysis or answer here.",
132398+ "action": "none" | "analyze_files",
132399+ "related_files": ["optional", "list", "of", "files"],
132400+ "complete": true | false
132401+ }
132402+
132403+ Explanation:
132404+ - If the provided code snippets and file tree are sufficient to confidently answer the question, set "complete": true and "action": "none".
132405+ - If you determine that you need to review additional files to provide a complete and accurate answer, set "complete": false, "action": "analyze_files", and list the related file paths you need to investigate further in "related_files".
132406+ - Do not invent file paths; only request files that logically relate to the question based on the information available.
132407+ - Always provide a "text_response" with your reasoning, even if requesting more files.
132408+
132409+ Important:
132410+ - **Respond only with the JSON object**, without any extra commentary or text outside of the JSON.
132411+
132412+ Information provided:
132413+ User's question:
132414+ ${commentBody}
132415+
132416+ File tree:
132417+ ${JSON.stringify(withoutContent, null, 2)}
132418+
132419+ Relevant code snippets:
132420+ ${relatedFiles.size > 0
132421+ ? Array.from(relatedFiles.entries()).map(([path, content]) => `\nFile: ${path}\nCode:\n${content}`).join('\n')
132422+ : chunks.map(chunk => `\nFile: ${chunk.path}\nCode:\n${chunk.chunk}`).join('\n')}
132423+ `;
132424+ const jsonResponse = await this.aiRepository.askJson(param.ai, prompt);
132425+ if (!jsonResponse) {
132426+ (0, logger_1.logError)(`No result from AI.`);
132427+ results.push(new result_1.Result({
132428+ id: this.taskId,
132429+ success: false,
132430+ executed: true,
132431+ steps: [
132432+ `Error in ${this.taskId}: No result from AI.`,
132433+ ],
132434+ }));
132435+ return results;
132436+ }
132437+ (0, logger_1.logInfo)(`🔎 Result: ${JSON.stringify(jsonResponse, null, 2)}`);
132438+ workComplete = jsonResponse.complete;
132439+ if (jsonResponse.action === 'analyze_files') {
132440+ relatedFiles = this.getRelatedFiles(jsonResponse.related_files, withContent);
132441+ }
132442+ else if (jsonResponse.action === 'none') {
132443+ finalResponse = jsonResponse.text_response;
132444+ }
132445+ }
132280132446 const totalDurationSeconds = (Date.now() - startTime) / 1000;
132281132447 (0, logger_1.logInfo)(`📦 🔎 Matched chunks for ${param.owner}/${param.repo}/${param.commit.branch}:\n Total duration: ${Math.ceil(totalDurationSeconds)} seconds`);
132282132448 results.push(new result_1.Result({
@@ -132304,6 +132470,30 @@ class AskActionUseCase {
132304132470 }
132305132471 return results;
132306132472 }
132473+ getRelatedFiles(relatedFiles, tree) {
132474+ const result = new Map();
132475+ const findFile = (node, targetPath) => {
132476+ if (node.path === targetPath) {
132477+ return node;
132478+ }
132479+ if (node.children) {
132480+ for (const child of node.children) {
132481+ const found = findFile(child, targetPath);
132482+ if (found) {
132483+ return found;
132484+ }
132485+ }
132486+ }
132487+ return null;
132488+ };
132489+ for (const filePath of relatedFiles) {
132490+ const fileNode = findFile(tree, filePath);
132491+ if (fileNode && fileNode.type === 'file' && fileNode.content) {
132492+ result.set(filePath, fileNode.content);
132493+ }
132494+ }
132495+ return result;
132496+ }
132307132497}
132308132498exports.AskActionUseCase = AskActionUseCase;
132309132499
0 commit comments