codereview-aia 0.1.2 → 0.1.4
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/dist/analysis/static/wpPhpcsRunner.d.ts +11 -0
- package/dist/analysis/static/wpPhpcsRunner.js +219 -0
- package/dist/analysis/static/wpPhpcsRunner.js.map +1 -0
- package/dist/clients/implementations/openRouterClient.js +2 -2
- package/dist/clients/implementations/openRouterClient.js.map +1 -1
- package/dist/clients/openRouterClient.js +2 -2
- package/dist/clients/openRouterClient.js.map +1 -1
- package/dist/clients/utils/promptFormatter.d.ts +3 -2
- package/dist/clients/utils/promptFormatter.js +82 -24
- package/dist/clients/utils/promptFormatter.js.map +1 -1
- package/dist/core/ConfigurationService.d.ts +21 -0
- package/dist/core/ConfigurationService.js +39 -0
- package/dist/core/ConfigurationService.js.map +1 -1
- package/dist/core/handlers/FileProcessingHandler.js +5 -0
- package/dist/core/handlers/FileProcessingHandler.js.map +1 -1
- package/dist/core/reviewOrchestrator.js +61 -1
- package/dist/core/reviewOrchestrator.js.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/cliEntry.js +57 -4
- package/dist/runtime/cliEntry.js.map +1 -1
- package/dist/runtime/fileCollector.d.ts +10 -1
- package/dist/runtime/fileCollector.js +217 -2
- package/dist/runtime/fileCollector.js.map +1 -1
- package/dist/runtime/reporting/markdownReportBuilder.d.ts +2 -0
- package/dist/runtime/reporting/markdownReportBuilder.js +57 -0
- package/dist/runtime/reporting/markdownReportBuilder.js.map +1 -1
- package/dist/runtime/reviewPipeline.d.ts +22 -3
- package/dist/runtime/reviewPipeline.js +46 -7
- package/dist/runtime/reviewPipeline.js.map +1 -1
- package/dist/runtime/runAiCodeReview.d.ts +19 -1
- package/dist/runtime/runAiCodeReview.js +243 -8
- package/dist/runtime/runAiCodeReview.js.map +1 -1
- package/dist/runtime/ui/RuntimeApp.js +15 -4
- package/dist/runtime/ui/RuntimeApp.js.map +1 -1
- package/dist/runtime/ui/screens/ProgressScreen.d.ts +6 -1
- package/dist/runtime/ui/screens/ProgressScreen.js +28 -2
- package/dist/runtime/ui/screens/ProgressScreen.js.map +1 -1
- package/dist/runtime/ui/screens/ResultsScreen.js +8 -1
- package/dist/runtime/ui/screens/ResultsScreen.js.map +1 -1
- package/dist/types/review.d.ts +60 -0
- package/dist/utils/detection/frameworkDetector.js +55 -0
- package/dist/utils/detection/frameworkDetector.js.map +1 -1
- package/dist/utils/promptTemplateManager.js +1 -0
- package/dist/utils/promptTemplateManager.js.map +1 -1
- package/package.json +13 -10
- package/.cr-aia.yml +0 -23
- package/.crignore +0 -0
- package/src/analysis/FindingsExtractor.ts +0 -431
- package/src/analysis/ai-detection/analyzers/BaseAnalyzer.ts +0 -267
- package/src/analysis/ai-detection/analyzers/DocumentationAnalyzer.ts +0 -622
- package/src/analysis/ai-detection/analyzers/GitHistoryAnalyzer.ts +0 -430
- package/src/analysis/ai-detection/core/AIDetectionEngine.ts +0 -467
- package/src/analysis/ai-detection/types/DetectionTypes.ts +0 -406
- package/src/analysis/ai-detection/utils/SubmissionConverter.ts +0 -390
- package/src/analysis/context/ReviewContext.ts +0 -378
- package/src/analysis/context/index.ts +0 -7
- package/src/analysis/index.ts +0 -8
- package/src/analysis/tokens/TokenAnalysisFormatter.ts +0 -154
- package/src/analysis/tokens/TokenAnalyzer.ts +0 -747
- package/src/analysis/tokens/index.ts +0 -8
- package/src/clients/base/abstractClient.ts +0 -190
- package/src/clients/base/httpClient.ts +0 -160
- package/src/clients/base/index.ts +0 -12
- package/src/clients/base/modelDetection.ts +0 -107
- package/src/clients/base/responseProcessor.ts +0 -586
- package/src/clients/factory/clientFactory.ts +0 -55
- package/src/clients/factory/index.ts +0 -8
- package/src/clients/implementations/index.ts +0 -8
- package/src/clients/implementations/openRouterClient.ts +0 -411
- package/src/clients/openRouterClient.ts +0 -863
- package/src/clients/openRouterClientWrapper.ts +0 -44
- package/src/clients/utils/directoryStructure.ts +0 -52
- package/src/clients/utils/index.ts +0 -11
- package/src/clients/utils/languageDetection.ts +0 -44
- package/src/clients/utils/promptFormatter.ts +0 -105
- package/src/clients/utils/promptLoader.ts +0 -53
- package/src/clients/utils/tokenCounter.ts +0 -297
- package/src/core/ApiClientSelector.ts +0 -37
- package/src/core/ConfigurationService.ts +0 -591
- package/src/core/ConsolidationService.ts +0 -423
- package/src/core/InteractiveDisplayManager.ts +0 -81
- package/src/core/OutputManager.ts +0 -275
- package/src/core/ReviewGenerator.ts +0 -140
- package/src/core/fileDiscovery.ts +0 -237
- package/src/core/handlers/EstimationHandler.ts +0 -104
- package/src/core/handlers/FileProcessingHandler.ts +0 -204
- package/src/core/handlers/OutputHandler.ts +0 -125
- package/src/core/handlers/ReviewExecutor.ts +0 -104
- package/src/core/reviewOrchestrator.ts +0 -333
- package/src/core/utils/ModelInfoUtils.ts +0 -56
- package/src/formatters/outputFormatter.ts +0 -62
- package/src/formatters/utils/IssueFormatters.ts +0 -83
- package/src/formatters/utils/JsonFormatter.ts +0 -77
- package/src/formatters/utils/MarkdownFormatters.ts +0 -609
- package/src/formatters/utils/MetadataFormatter.ts +0 -269
- package/src/formatters/utils/ModelInfoExtractor.ts +0 -115
- package/src/index.ts +0 -28
- package/src/plugins/PluginInterface.ts +0 -50
- package/src/plugins/PluginManager.ts +0 -126
- package/src/prompts/PromptManager.ts +0 -69
- package/src/prompts/cache/PromptCache.ts +0 -50
- package/src/prompts/promptText/common/variables/css-frameworks.json +0 -33
- package/src/prompts/promptText/common/variables/framework-versions.json +0 -45
- package/src/prompts/promptText/frameworks/react/comprehensive.hbs +0 -19
- package/src/prompts/promptText/languages/css/comprehensive.hbs +0 -18
- package/src/prompts/promptText/languages/generic/comprehensive.hbs +0 -20
- package/src/prompts/promptText/languages/html/comprehensive.hbs +0 -18
- package/src/prompts/promptText/languages/javascript/comprehensive.hbs +0 -18
- package/src/prompts/promptText/languages/python/comprehensive.hbs +0 -18
- package/src/prompts/promptText/languages/typescript/comprehensive.hbs +0 -18
- package/src/runtime/auth/service.ts +0 -58
- package/src/runtime/auth/session.ts +0 -103
- package/src/runtime/auth/types.ts +0 -11
- package/src/runtime/cliEntry.ts +0 -196
- package/src/runtime/debug/logManager.ts +0 -37
- package/src/runtime/errors.ts +0 -13
- package/src/runtime/fileCollector.ts +0 -222
- package/src/runtime/manifest.ts +0 -64
- package/src/runtime/openrouterProxy.ts +0 -45
- package/src/runtime/preprod/webCheck.ts +0 -104
- package/src/runtime/proxyConfig.ts +0 -94
- package/src/runtime/proxyEnvironment.ts +0 -71
- package/src/runtime/reportMerge.ts +0 -102
- package/src/runtime/reporting/markdownReportBuilder.ts +0 -138
- package/src/runtime/reporting/reportDataCollector.ts +0 -234
- package/src/runtime/reporting/summaryGenerator.ts +0 -86
- package/src/runtime/reviewPipeline.ts +0 -161
- package/src/runtime/runAiCodeReview.ts +0 -153
- package/src/runtime/runtimeConfig.ts +0 -5
- package/src/runtime/ui/Layout.tsx +0 -57
- package/src/runtime/ui/RuntimeApp.tsx +0 -233
- package/src/runtime/ui/inkModules.ts +0 -73
- package/src/runtime/ui/screens/AuthScreen.tsx +0 -128
- package/src/runtime/ui/screens/ModeSelection.tsx +0 -185
- package/src/runtime/ui/screens/ProgressScreen.tsx +0 -62
- package/src/runtime/ui/screens/ResultsScreen.tsx +0 -83
- package/src/strategies/ArchitecturalReviewStrategy.ts +0 -54
- package/src/strategies/CodingTestReviewStrategy.ts +0 -920
- package/src/strategies/ConsolidatedReviewStrategy.ts +0 -59
- package/src/strategies/ExtractPatternsReviewStrategy.ts +0 -64
- package/src/strategies/MultiPassReviewStrategy.ts +0 -785
- package/src/strategies/ReviewStrategy.ts +0 -64
- package/src/strategies/StrategyFactory.ts +0 -79
- package/src/strategies/index.ts +0 -14
- package/src/tokenizers/baseTokenizer.ts +0 -61
- package/src/tokenizers/gptTokenizer.ts +0 -27
- package/src/tokenizers/index.ts +0 -8
- package/src/types/apiResponses.ts +0 -40
- package/src/types/cli.ts +0 -24
- package/src/types/common.ts +0 -39
- package/src/types/configuration.ts +0 -201
- package/src/types/handlebars.d.ts +0 -5
- package/src/types/patch.d.ts +0 -25
- package/src/types/review.ts +0 -294
- package/src/types/reviewContext.d.ts +0 -65
- package/src/types/reviewSchema.ts +0 -181
- package/src/types/structuredReview.ts +0 -167
- package/src/types/tokenAnalysis.ts +0 -56
- package/src/utils/FileReader.ts +0 -93
- package/src/utils/FileWriter.ts +0 -76
- package/src/utils/PathGenerator.ts +0 -97
- package/src/utils/api/apiUtils.ts +0 -14
- package/src/utils/api/index.ts +0 -1
- package/src/utils/apiErrorHandler.ts +0 -287
- package/src/utils/ciDataCollector.ts +0 -252
- package/src/utils/codingTestConfigLoader.ts +0 -466
- package/src/utils/dependencies/aiDependencyAnalyzer.ts +0 -454
- package/src/utils/detection/frameworkDetector.ts +0 -879
- package/src/utils/detection/index.ts +0 -10
- package/src/utils/detection/projectTypeDetector.ts +0 -518
- package/src/utils/diagramGenerator.ts +0 -206
- package/src/utils/errorLogger.ts +0 -60
- package/src/utils/estimationUtils.ts +0 -407
- package/src/utils/fileFilters.ts +0 -373
- package/src/utils/fileSystem.ts +0 -57
- package/src/utils/index.ts +0 -36
- package/src/utils/logger.ts +0 -290
- package/src/utils/pathValidator.ts +0 -98
- package/src/utils/priorityFilter.ts +0 -59
- package/src/utils/projectDocs.ts +0 -189
- package/src/utils/promptPaths.ts +0 -29
- package/src/utils/promptTemplateManager.ts +0 -157
- package/src/utils/review/consolidateReview.ts +0 -553
- package/src/utils/review/fixDisplay.ts +0 -100
- package/src/utils/review/fixImplementation.ts +0 -61
- package/src/utils/review/index.ts +0 -36
- package/src/utils/review/interactiveProcessing.ts +0 -294
- package/src/utils/review/progressTracker.ts +0 -296
- package/src/utils/review/reviewExtraction.ts +0 -382
- package/src/utils/review/types.ts +0 -46
- package/src/utils/reviewActionHandler.ts +0 -18
- package/src/utils/reviewParser.ts +0 -253
- package/src/utils/sanitizer.ts +0 -238
- package/src/utils/smartFileSelector.ts +0 -255
- package/src/utils/templateLoader.ts +0 -514
- package/src/utils/treeGenerator.ts +0 -153
- package/tsconfig.build.json +0 -14
- package/tsconfig.json +0 -59
|
@@ -1,863 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Client for interacting with the OpenRouter API.
|
|
3
|
-
*
|
|
4
|
-
* This module provides a client for interacting with OpenRouter's API, which gives
|
|
5
|
-
* access to a variety of AI models from different providers. It handles API key
|
|
6
|
-
* management, request formatting, response processing, rate limiting, error handling,
|
|
7
|
-
* and cost estimation for code reviews.
|
|
8
|
-
*
|
|
9
|
-
* Key features:
|
|
10
|
-
* - Support for various models through OpenRouter (Claude, GPT-4, etc.)
|
|
11
|
-
* - Streaming and non-streaming responses
|
|
12
|
-
* - Robust error handling and rate limit management
|
|
13
|
-
* - Cost estimation for API usage
|
|
14
|
-
* - Support for different review types
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import type { CostInfo, FileInfo, ReviewOptions, ReviewResult, ReviewType } from '../types/review';
|
|
18
|
-
import { ApiError, TokenLimitError } from '../utils/apiErrorHandler';
|
|
19
|
-
import { getConfig } from '../core/ConfigurationService';
|
|
20
|
-
import logger from '../utils/logger';
|
|
21
|
-
import type { ProjectDocs /* , addProjectDocsToPrompt */ } from '../utils/projectDocs'; // addProjectDocsToPrompt not used
|
|
22
|
-
import {
|
|
23
|
-
buildOpenRouterProxyHeaders,
|
|
24
|
-
resolveOpenRouterProxyUrl,
|
|
25
|
-
withProxyMetadata,
|
|
26
|
-
} from '../runtime/openrouterProxy';
|
|
27
|
-
// import { generateDirectoryStructure } from './utils'; // Not used in this file
|
|
28
|
-
// import { getLanguageFromExtension } from './utils/languageDetection'; // Not used in this file
|
|
29
|
-
import {
|
|
30
|
-
formatConsolidatedReviewPrompt,
|
|
31
|
-
formatSingleFileReviewPrompt,
|
|
32
|
-
} from './utils/promptFormatter';
|
|
33
|
-
import { loadPromptTemplate } from './utils/promptLoader';
|
|
34
|
-
import { getCostInfoFromText } from './utils/tokenCounter';
|
|
35
|
-
|
|
36
|
-
// Track if we've initialized a model successfully
|
|
37
|
-
let modelInitialized = false;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Determine appropriate max_tokens based on review type and context
|
|
42
|
-
*/
|
|
43
|
-
function getMaxTokensForReviewType(
|
|
44
|
-
reviewType?: string,
|
|
45
|
-
isConsolidation?: boolean,
|
|
46
|
-
): number | undefined {
|
|
47
|
-
// For consolidation passes, we need unlimited tokens to avoid truncation
|
|
48
|
-
if (isConsolidation) {
|
|
49
|
-
return undefined; // No limit for consolidating multiple reviews
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// For different review types, adjust token limits based on expected output size
|
|
53
|
-
switch (reviewType) {
|
|
54
|
-
case 'consolidated':
|
|
55
|
-
return undefined; // No limit for consolidated reviews to avoid truncation
|
|
56
|
-
case 'architectural':
|
|
57
|
-
return 8000; // Architectural reviews can be detailed
|
|
58
|
-
case 'security':
|
|
59
|
-
return 8000; // Security reviews can be detailed
|
|
60
|
-
case 'performance':
|
|
61
|
-
return 8000; // Performance reviews can be detailed
|
|
62
|
-
case 'quick-fixes':
|
|
63
|
-
return 8000; // Quick fixes can be substantial for large codebases
|
|
64
|
-
default:
|
|
65
|
-
return 8000; // Default to higher limit for unknown types
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Detect if response was truncated due to token limits
|
|
71
|
-
*/
|
|
72
|
-
function isResponseTruncated(content: string, finishReason?: string): boolean {
|
|
73
|
-
if (finishReason === 'length' || finishReason === 'MAX_TOKENS') {
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Check for common truncation patterns in JSON responses
|
|
78
|
-
const trimmed = content.trim();
|
|
79
|
-
if (trimmed.startsWith('{') || trimmed.startsWith('```json')) {
|
|
80
|
-
// Look for incomplete JSON structures
|
|
81
|
-
if (!trimmed.endsWith('}') && !trimmed.endsWith('```')) {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Count braces to detect incomplete JSON
|
|
86
|
-
const openBraces = (trimmed.match(/\{/g) || []).length;
|
|
87
|
-
const closeBraces = (trimmed.match(/\}/g) || []).length;
|
|
88
|
-
if (openBraces > closeBraces) {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Helper function to check if this is the correct client for the selected model
|
|
97
|
-
function isOpenRouterModel(): {
|
|
98
|
-
isCorrect: boolean;
|
|
99
|
-
adapter: string;
|
|
100
|
-
modelName: string;
|
|
101
|
-
} {
|
|
102
|
-
// Get the model from configuration (CLI override or env)
|
|
103
|
-
const selectedModel = getConfig().model || '';
|
|
104
|
-
|
|
105
|
-
// Parse the model name
|
|
106
|
-
const [adapter, modelName] = selectedModel.includes(':')
|
|
107
|
-
? selectedModel.split(':')
|
|
108
|
-
: ['openrouter', selectedModel];
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
isCorrect: adapter === 'openrouter',
|
|
112
|
-
adapter,
|
|
113
|
-
modelName,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// This function was removed as it's no longer needed with the improved client selection logic
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Initialize the OpenRouter client
|
|
121
|
-
* @returns Promise resolving to a boolean indicating if initialization was successful
|
|
122
|
-
*/
|
|
123
|
-
export async function initializeAnyOpenRouterModel(): Promise<boolean> {
|
|
124
|
-
const { isCorrect, modelName } = isOpenRouterModel();
|
|
125
|
-
|
|
126
|
-
// If this is not an OpenRouter model, just return true without initializing
|
|
127
|
-
if (!isCorrect) {
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// If we've already initialized, return true
|
|
132
|
-
if (modelInitialized) {
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
logger.debug(`Initializing OpenRouter model: ${modelName}...`);
|
|
138
|
-
|
|
139
|
-
// Mark as initialized
|
|
140
|
-
modelInitialized = true;
|
|
141
|
-
logger.debug(`Successfully initialized OpenRouter model: ${modelName}`);
|
|
142
|
-
return true;
|
|
143
|
-
} catch (error) {
|
|
144
|
-
console.error(`Error initializing OpenRouter model ${modelName}:`, error);
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Generate a code review using the OpenRouter API
|
|
151
|
-
* @param fileContent Content of the file to review
|
|
152
|
-
* @param filePath Path to the file
|
|
153
|
-
* @param reviewType Type of review to perform
|
|
154
|
-
* @param projectDocs Optional project documentation
|
|
155
|
-
* @param options Review options
|
|
156
|
-
* @returns Promise resolving to the review result
|
|
157
|
-
*/
|
|
158
|
-
export async function generateOpenRouterReview(
|
|
159
|
-
fileContent: string,
|
|
160
|
-
filePath: string,
|
|
161
|
-
reviewType: ReviewType,
|
|
162
|
-
projectDocs?: ProjectDocs | null,
|
|
163
|
-
options?: ReviewOptions,
|
|
164
|
-
): Promise<ReviewResult> {
|
|
165
|
-
try {
|
|
166
|
-
// Initialize the model if we haven't already
|
|
167
|
-
if (!modelInitialized) {
|
|
168
|
-
await initializeAnyOpenRouterModel();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Use the imported dependencies
|
|
172
|
-
|
|
173
|
-
// Get API key from environment variables
|
|
174
|
-
let content: string;
|
|
175
|
-
let cost: CostInfo | undefined;
|
|
176
|
-
|
|
177
|
-
// Get the language from the file extension
|
|
178
|
-
// const language = getLanguageFromExtension(filePath); // Currently unused
|
|
179
|
-
|
|
180
|
-
// Load the appropriate prompt template
|
|
181
|
-
const promptTemplate = await loadPromptTemplate(reviewType, options);
|
|
182
|
-
|
|
183
|
-
// Format the prompt
|
|
184
|
-
const prompt = formatSingleFileReviewPrompt(promptTemplate, fileContent, filePath, projectDocs);
|
|
185
|
-
|
|
186
|
-
// For consolidation mode, use the writer model if specified, otherwise use the review model
|
|
187
|
-
let modelName: string;
|
|
188
|
-
if (options?.isConsolidation) {
|
|
189
|
-
// During consolidation, the model is already set in the environment by consolidateReview.ts
|
|
190
|
-
const consolidationModel = process.env.AI_CODE_REVIEW_MODEL || '';
|
|
191
|
-
const [, model] = consolidationModel.includes(':')
|
|
192
|
-
? consolidationModel.split(':')
|
|
193
|
-
: ['openrouter', consolidationModel];
|
|
194
|
-
modelName = model;
|
|
195
|
-
logger.debug(`[OpenRouter] Using consolidation model for single file: ${modelName}`);
|
|
196
|
-
} else {
|
|
197
|
-
// Regular review - use the configured model
|
|
198
|
-
const result = isOpenRouterModel();
|
|
199
|
-
modelName = result.modelName;
|
|
200
|
-
logger.debug(`[OpenRouter] Using review model for single file: ${modelName}`);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
logger.debug(`Generating review with OpenRouter ${modelName}...`);
|
|
205
|
-
|
|
206
|
-
const requestMaxTokens = getMaxTokensForReviewType(reviewType, options?.isConsolidation);
|
|
207
|
-
const payload = withProxyMetadata({
|
|
208
|
-
model: modelName,
|
|
209
|
-
messages: [
|
|
210
|
-
{
|
|
211
|
-
role: 'system',
|
|
212
|
-
content: `You are an expert code reviewer. Focus on providing actionable feedback. IMPORTANT: DO NOT REPEAT THE INSTRUCTIONS IN YOUR RESPONSE. DO NOT ASK FOR CODE TO REVIEW. ASSUME THE CODE IS ALREADY PROVIDED IN THE USER MESSAGE. FOCUS ONLY ON PROVIDING THE CODE REVIEW CONTENT.
|
|
213
|
-
|
|
214
|
-
IMPORTANT: Your response MUST be in the following JSON format:
|
|
215
|
-
|
|
216
|
-
{
|
|
217
|
-
"summary": "A brief summary of the code review",
|
|
218
|
-
"issues": [
|
|
219
|
-
{
|
|
220
|
-
"title": "Issue title",
|
|
221
|
-
"priority": "high|medium|low",
|
|
222
|
-
"type": "bug|security|performance|maintainability|readability|architecture|best-practice|documentation|testing|other",
|
|
223
|
-
"filePath": "Path to the file",
|
|
224
|
-
"lineNumbers": "Line number or range (e.g., 10 or 10-15)",
|
|
225
|
-
"description": "Detailed description of the issue",
|
|
226
|
-
"codeSnippet": "Relevant code snippet",
|
|
227
|
-
"suggestedFix": "Suggested code fix",
|
|
228
|
-
"impact": "Impact of the issue"
|
|
229
|
-
}
|
|
230
|
-
],
|
|
231
|
-
"recommendations": [
|
|
232
|
-
"General recommendation 1",
|
|
233
|
-
"General recommendation 2"
|
|
234
|
-
],
|
|
235
|
-
"positiveAspects": [
|
|
236
|
-
"Positive aspect 1",
|
|
237
|
-
"Positive aspect 2"
|
|
238
|
-
]
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
Ensure your response is valid JSON. Do not include any text outside the JSON structure.`,
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
role: 'user',
|
|
245
|
-
content: prompt,
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
temperature: 0.2,
|
|
249
|
-
...(requestMaxTokens ? { max_tokens: requestMaxTokens } : {}),
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Make the API request via proxy
|
|
253
|
-
const response = await fetch(resolveOpenRouterProxyUrl(), {
|
|
254
|
-
method: 'POST',
|
|
255
|
-
headers: buildOpenRouterProxyHeaders(),
|
|
256
|
-
body: JSON.stringify(payload),
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
if (!response.ok) {
|
|
260
|
-
const errorData = await response.json();
|
|
261
|
-
|
|
262
|
-
// Check for token limit errors
|
|
263
|
-
const errorMessage = JSON.stringify(errorData).toLowerCase();
|
|
264
|
-
if (
|
|
265
|
-
errorMessage.includes('token') &&
|
|
266
|
-
(errorMessage.includes('limit') ||
|
|
267
|
-
errorMessage.includes('exceed') ||
|
|
268
|
-
errorMessage.includes('too long') ||
|
|
269
|
-
errorMessage.includes('too many'))
|
|
270
|
-
) {
|
|
271
|
-
// Extract token count from prompt if possible
|
|
272
|
-
const { countTokens } = await import('../tokenizers');
|
|
273
|
-
const tokenCount = countTokens(prompt, modelName);
|
|
274
|
-
|
|
275
|
-
throw new TokenLimitError(
|
|
276
|
-
`Token limit exceeded for model ${modelName}. Content has ${tokenCount.toLocaleString()} tokens. Consider using --multi-pass flag for large codebases.`,
|
|
277
|
-
tokenCount,
|
|
278
|
-
undefined,
|
|
279
|
-
response.status,
|
|
280
|
-
errorData,
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
throw new Error(`OpenRouter API error: ${JSON.stringify(errorData)}`);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const data = await response.json();
|
|
288
|
-
|
|
289
|
-
// Enhanced logging for debugging empty content issue
|
|
290
|
-
const finishReason = data.choices?.[0]?.finish_reason;
|
|
291
|
-
const responseContent = data.choices?.[0]?.message?.content || '';
|
|
292
|
-
const isTruncated = isResponseTruncated(responseContent, finishReason);
|
|
293
|
-
|
|
294
|
-
logger.debug(`[OpenRouter] API Response structure:`, {
|
|
295
|
-
hasChoices: !!data.choices,
|
|
296
|
-
choicesLength: data.choices?.length || 0,
|
|
297
|
-
firstChoiceExists: !!(data.choices && data.choices[0]),
|
|
298
|
-
firstChoiceMessage: data.choices?.[0]?.message ? 'exists' : 'missing',
|
|
299
|
-
contentExists: !!responseContent,
|
|
300
|
-
contentLength: responseContent.length,
|
|
301
|
-
contentPreview: responseContent.substring(0, 100) || 'N/A',
|
|
302
|
-
finishReason: finishReason || 'unknown',
|
|
303
|
-
isTruncated: isTruncated,
|
|
304
|
-
maxTokensUsed:
|
|
305
|
-
getMaxTokensForReviewType(reviewType, options?.isConsolidation) || 'unlimited',
|
|
306
|
-
fullResponse: JSON.stringify(data).substring(0, 500) + '...',
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
if (data.choices && data.choices.length > 0) {
|
|
310
|
-
content = data.choices[0].message.content;
|
|
311
|
-
|
|
312
|
-
// Critical check for empty content
|
|
313
|
-
if (!content || content.trim().length === 0) {
|
|
314
|
-
logger.error(
|
|
315
|
-
`[OpenRouter] CRITICAL: API returned successful response but content is empty!`,
|
|
316
|
-
);
|
|
317
|
-
logger.error(`[OpenRouter] Response details:`, {
|
|
318
|
-
modelName,
|
|
319
|
-
promptLength: prompt.length,
|
|
320
|
-
responseStatus: response.status,
|
|
321
|
-
responseHeaders: Object.fromEntries(response.headers.entries()),
|
|
322
|
-
fullApiResponse: JSON.stringify(data, null, 2),
|
|
323
|
-
});
|
|
324
|
-
throw new Error(`OpenRouter API returned empty content for model ${modelName}`);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Check for truncated responses
|
|
328
|
-
if (isTruncated) {
|
|
329
|
-
logger.warn(`[OpenRouter] WARNING: Response appears to be truncated!`);
|
|
330
|
-
logger.warn(`[OpenRouter] Truncation details:`, {
|
|
331
|
-
modelName,
|
|
332
|
-
finishReason,
|
|
333
|
-
contentLength: content.length,
|
|
334
|
-
maxTokensRequested:
|
|
335
|
-
getMaxTokensForReviewType(reviewType, options?.isConsolidation) || 'unlimited',
|
|
336
|
-
reviewType,
|
|
337
|
-
isConsolidation: options?.isConsolidation || false,
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
// For now, we'll continue with the truncated response but log it
|
|
341
|
-
// In the future, we could implement retry logic with shorter prompts
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
logger.debug(`Successfully generated review with OpenRouter ${modelName}`);
|
|
345
|
-
} else {
|
|
346
|
-
logger.error(`[OpenRouter] Invalid response format:`, JSON.stringify(data, null, 2));
|
|
347
|
-
throw new Error(`Invalid response format from OpenRouter ${modelName}`);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Calculate cost information
|
|
351
|
-
try {
|
|
352
|
-
cost = getCostInfoFromText(prompt, content, `openrouter:${modelName}`);
|
|
353
|
-
} catch (error) {
|
|
354
|
-
logger.warn(
|
|
355
|
-
`Failed to calculate cost information: ${
|
|
356
|
-
error instanceof Error ? error.message : String(error)
|
|
357
|
-
}`,
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
} catch (error) {
|
|
361
|
-
// Re-throw TokenLimitError with additional context
|
|
362
|
-
if (error instanceof TokenLimitError) {
|
|
363
|
-
throw error;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
throw new ApiError(
|
|
367
|
-
`Failed to generate review with OpenRouter ${modelName}: ${
|
|
368
|
-
error instanceof Error ? error.message : String(error)
|
|
369
|
-
}`,
|
|
370
|
-
);
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Try to parse the response as JSON with robust error recovery
|
|
374
|
-
let structuredData = null;
|
|
375
|
-
try {
|
|
376
|
-
// Try multiple strategies to extract JSON from the response
|
|
377
|
-
const jsonExtractionStrategies = [
|
|
378
|
-
// Strategy 1: Look for JSON in markdown code blocks
|
|
379
|
-
() => {
|
|
380
|
-
const patterns = [
|
|
381
|
-
/```(?:json)?\s*([\s\S]*?)\s*```/,
|
|
382
|
-
/```(?:typescript|javascript|ts|js)?\s*([\s\S]*?)\s*```/,
|
|
383
|
-
];
|
|
384
|
-
for (const pattern of patterns) {
|
|
385
|
-
const match = content.match(pattern);
|
|
386
|
-
if (match && match[1]) {
|
|
387
|
-
const extracted = match[1].trim();
|
|
388
|
-
if (extracted.startsWith('{') && extracted.endsWith('}')) {
|
|
389
|
-
return extracted;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return null;
|
|
394
|
-
},
|
|
395
|
-
|
|
396
|
-
// Strategy 2: Find balanced JSON object
|
|
397
|
-
() => {
|
|
398
|
-
const startIdx = content.indexOf('{');
|
|
399
|
-
if (startIdx === -1) return null;
|
|
400
|
-
|
|
401
|
-
let depth = 0;
|
|
402
|
-
let inString = false;
|
|
403
|
-
let escapeNext = false;
|
|
404
|
-
|
|
405
|
-
for (let i = startIdx; i < content.length; i++) {
|
|
406
|
-
const char = content[i];
|
|
407
|
-
|
|
408
|
-
if (escapeNext) {
|
|
409
|
-
escapeNext = false;
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (char === '\\') {
|
|
414
|
-
escapeNext = true;
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (char === '"' && !escapeNext) {
|
|
419
|
-
inString = !inString;
|
|
420
|
-
continue;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (!inString) {
|
|
424
|
-
if (char === '{') depth++;
|
|
425
|
-
else if (char === '}') {
|
|
426
|
-
depth--;
|
|
427
|
-
if (depth === 0) {
|
|
428
|
-
return content.substring(startIdx, i + 1);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// If we couldn't find balanced braces, try to fix unterminated strings
|
|
435
|
-
if (inString) {
|
|
436
|
-
// Add a closing quote and try to close the object
|
|
437
|
-
return content.substring(startIdx) + '"}';
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return null;
|
|
441
|
-
},
|
|
442
|
-
|
|
443
|
-
// Strategy 3: Use the content as-is
|
|
444
|
-
() => content,
|
|
445
|
-
];
|
|
446
|
-
|
|
447
|
-
let jsonContent = null;
|
|
448
|
-
for (const strategy of jsonExtractionStrategies) {
|
|
449
|
-
try {
|
|
450
|
-
const extracted = strategy();
|
|
451
|
-
if (extracted) {
|
|
452
|
-
// Try to parse the extracted content
|
|
453
|
-
structuredData = JSON.parse(extracted);
|
|
454
|
-
|
|
455
|
-
// Validate that it has the expected structure
|
|
456
|
-
if (structuredData && typeof structuredData === 'object') {
|
|
457
|
-
jsonContent = extracted;
|
|
458
|
-
logger.debug('Successfully extracted and parsed JSON');
|
|
459
|
-
break;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
} catch (err) {
|
|
463
|
-
// Continue to next strategy
|
|
464
|
-
logger.debug(
|
|
465
|
-
`JSON extraction strategy failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// If we successfully parsed JSON, validate its structure
|
|
471
|
-
if (structuredData && !structuredData.summary && !Array.isArray(structuredData.issues)) {
|
|
472
|
-
logger.warn('Response is valid JSON but does not have the expected structure');
|
|
473
|
-
// Still keep the structured data as it might have partial information
|
|
474
|
-
}
|
|
475
|
-
} catch (parseError) {
|
|
476
|
-
logger.warn(
|
|
477
|
-
`Failed to parse response as JSON after all recovery attempts: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
|
|
478
|
-
);
|
|
479
|
-
// Keep the original response as content
|
|
480
|
-
structuredData = null;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// Return the review result
|
|
484
|
-
return {
|
|
485
|
-
content,
|
|
486
|
-
cost,
|
|
487
|
-
modelUsed: `openrouter:${modelName}`,
|
|
488
|
-
filePath,
|
|
489
|
-
reviewType,
|
|
490
|
-
timestamp: new Date().toISOString(),
|
|
491
|
-
structuredData,
|
|
492
|
-
};
|
|
493
|
-
} catch (error) {
|
|
494
|
-
logger.error(
|
|
495
|
-
`Error generating review for ${filePath}: ${
|
|
496
|
-
error instanceof Error ? error.message : String(error)
|
|
497
|
-
}`,
|
|
498
|
-
);
|
|
499
|
-
throw error;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Generate a consolidated review for multiple files
|
|
505
|
-
* @param files Array of file information objects
|
|
506
|
-
* @param projectName Name of the project
|
|
507
|
-
* @param reviewType Type of review to perform
|
|
508
|
-
* @param projectDocs Optional project documentation
|
|
509
|
-
* @param options Review options
|
|
510
|
-
* @returns Promise resolving to the review result
|
|
511
|
-
*/
|
|
512
|
-
export async function generateOpenRouterConsolidatedReview(
|
|
513
|
-
files: FileInfo[],
|
|
514
|
-
projectName: string,
|
|
515
|
-
reviewType: ReviewType,
|
|
516
|
-
projectDocs?: ProjectDocs | null,
|
|
517
|
-
options?: ReviewOptions,
|
|
518
|
-
): Promise<ReviewResult> {
|
|
519
|
-
try {
|
|
520
|
-
// Initialize the model if we haven't already
|
|
521
|
-
if (!modelInitialized) {
|
|
522
|
-
await initializeAnyOpenRouterModel();
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Use the imported dependencies
|
|
526
|
-
|
|
527
|
-
// Get API key from environment variables
|
|
528
|
-
let content: string;
|
|
529
|
-
let cost: CostInfo | undefined;
|
|
530
|
-
|
|
531
|
-
// Load the appropriate prompt template
|
|
532
|
-
const promptTemplate = await loadPromptTemplate(reviewType, options);
|
|
533
|
-
|
|
534
|
-
// Format the prompt
|
|
535
|
-
const prompt = formatConsolidatedReviewPrompt(
|
|
536
|
-
promptTemplate,
|
|
537
|
-
projectName,
|
|
538
|
-
files.map((file) => ({
|
|
539
|
-
relativePath: file.relativePath || '',
|
|
540
|
-
content: file.content,
|
|
541
|
-
sizeInBytes: file.content.length,
|
|
542
|
-
})),
|
|
543
|
-
projectDocs,
|
|
544
|
-
);
|
|
545
|
-
|
|
546
|
-
// For consolidation mode, use the writer model if specified, otherwise use the review model
|
|
547
|
-
let modelName: string;
|
|
548
|
-
if (options?.isConsolidation) {
|
|
549
|
-
// During consolidation, the model is already set in the environment by consolidateReview.ts
|
|
550
|
-
const consolidationModel = process.env.AI_CODE_REVIEW_MODEL || '';
|
|
551
|
-
const [, model] = consolidationModel.includes(':')
|
|
552
|
-
? consolidationModel.split(':')
|
|
553
|
-
: ['openrouter', consolidationModel];
|
|
554
|
-
modelName = model;
|
|
555
|
-
logger.debug(`[OpenRouter] Using consolidation model: ${modelName}`);
|
|
556
|
-
} else {
|
|
557
|
-
// Regular review - use the configured model
|
|
558
|
-
const result = isOpenRouterModel();
|
|
559
|
-
modelName = result.modelName;
|
|
560
|
-
logger.debug(`[OpenRouter] Using review model: ${modelName}`);
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Validate that we have a non-empty model name
|
|
564
|
-
if (!modelName || modelName.trim() === '') {
|
|
565
|
-
throw new Error(
|
|
566
|
-
`Invalid or empty model name: '${modelName}'. Check your model configuration.`,
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
try {
|
|
571
|
-
logger.debug(`Generating consolidated review with OpenRouter ${modelName}...`);
|
|
572
|
-
|
|
573
|
-
const consolidationMaxTokens = getMaxTokensForReviewType(reviewType, options?.isConsolidation);
|
|
574
|
-
const payload = withProxyMetadata({
|
|
575
|
-
model: modelName,
|
|
576
|
-
messages: [
|
|
577
|
-
{
|
|
578
|
-
role: 'system',
|
|
579
|
-
content: `You are an expert code reviewer. Focus on providing actionable feedback. IMPORTANT: DO NOT REPEAT THE INSTRUCTIONS IN YOUR RESPONSE. DO NOT ASK FOR CODE TO REVIEW. ASSUME THE CODE IS ALREADY PROVIDED IN THE USER MESSAGE. FOCUS ONLY ON PROVIDING THE CODE REVIEW CONTENT.
|
|
580
|
-
|
|
581
|
-
IMPORTANT: Your response MUST be in the following JSON format:
|
|
582
|
-
|
|
583
|
-
{
|
|
584
|
-
"summary": "A brief summary of the code review",
|
|
585
|
-
"issues": [
|
|
586
|
-
{
|
|
587
|
-
"title": "Issue title",
|
|
588
|
-
"priority": "high|medium|low",
|
|
589
|
-
"type": "bug|security|performance|maintainability|readability|architecture|best-practice|documentation|testing|other",
|
|
590
|
-
"filePath": "Path to the file",
|
|
591
|
-
"lineNumbers": "Line number or range (e.g., 10 or 10-15)",
|
|
592
|
-
"description": "Detailed description of the issue",
|
|
593
|
-
"codeSnippet": "Relevant code snippet",
|
|
594
|
-
"suggestedFix": "Suggested code fix",
|
|
595
|
-
"impact": "Impact of the issue"
|
|
596
|
-
}
|
|
597
|
-
],
|
|
598
|
-
"recommendations": [
|
|
599
|
-
"General recommendation 1",
|
|
600
|
-
"General recommendation 2"
|
|
601
|
-
],
|
|
602
|
-
"positiveAspects": [
|
|
603
|
-
"Positive aspect 1",
|
|
604
|
-
"Positive aspect 2"
|
|
605
|
-
]
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
Ensure your response is valid JSON. Do not include any text outside the JSON structure.`,
|
|
609
|
-
},
|
|
610
|
-
{
|
|
611
|
-
role: 'user',
|
|
612
|
-
content: prompt,
|
|
613
|
-
},
|
|
614
|
-
],
|
|
615
|
-
temperature: 0.2,
|
|
616
|
-
...(consolidationMaxTokens ? { max_tokens: consolidationMaxTokens } : {}),
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
// Make the API request
|
|
620
|
-
const response = await fetch(resolveOpenRouterProxyUrl(), {
|
|
621
|
-
method: 'POST',
|
|
622
|
-
headers: buildOpenRouterProxyHeaders(),
|
|
623
|
-
body: JSON.stringify(payload),
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
if (!response.ok) {
|
|
627
|
-
const errorData = await response.json();
|
|
628
|
-
|
|
629
|
-
// Check for token limit errors
|
|
630
|
-
const errorMessage = JSON.stringify(errorData).toLowerCase();
|
|
631
|
-
if (
|
|
632
|
-
errorMessage.includes('token') &&
|
|
633
|
-
(errorMessage.includes('limit') ||
|
|
634
|
-
errorMessage.includes('exceed') ||
|
|
635
|
-
errorMessage.includes('too long') ||
|
|
636
|
-
errorMessage.includes('too many'))
|
|
637
|
-
) {
|
|
638
|
-
// Extract token count from prompt if possible
|
|
639
|
-
const { countTokens } = await import('../tokenizers');
|
|
640
|
-
const tokenCount = countTokens(prompt, modelName);
|
|
641
|
-
|
|
642
|
-
throw new TokenLimitError(
|
|
643
|
-
`Token limit exceeded for model ${modelName}. Content has ${tokenCount.toLocaleString()} tokens. Consider using --multi-pass flag for large codebases.`,
|
|
644
|
-
tokenCount,
|
|
645
|
-
undefined,
|
|
646
|
-
response.status,
|
|
647
|
-
errorData,
|
|
648
|
-
);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
throw new Error(`OpenRouter API error: ${JSON.stringify(errorData)}`);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
const data = await response.json();
|
|
655
|
-
|
|
656
|
-
// Enhanced logging for debugging empty content issue
|
|
657
|
-
const finishReason = data.choices?.[0]?.finish_reason;
|
|
658
|
-
const responseContent = data.choices?.[0]?.message?.content || '';
|
|
659
|
-
const isTruncated = isResponseTruncated(responseContent, finishReason);
|
|
660
|
-
|
|
661
|
-
logger.debug(`[OpenRouter] API Response structure:`, {
|
|
662
|
-
hasChoices: !!data.choices,
|
|
663
|
-
choicesLength: data.choices?.length || 0,
|
|
664
|
-
firstChoiceExists: !!(data.choices && data.choices[0]),
|
|
665
|
-
firstChoiceMessage: data.choices?.[0]?.message ? 'exists' : 'missing',
|
|
666
|
-
contentExists: !!responseContent,
|
|
667
|
-
contentLength: responseContent.length,
|
|
668
|
-
contentPreview: responseContent.substring(0, 100) || 'N/A',
|
|
669
|
-
finishReason: finishReason || 'unknown',
|
|
670
|
-
isTruncated: isTruncated,
|
|
671
|
-
maxTokensUsed:
|
|
672
|
-
getMaxTokensForReviewType(reviewType, options?.isConsolidation) || 'unlimited',
|
|
673
|
-
fullResponse: JSON.stringify(data).substring(0, 500) + '...',
|
|
674
|
-
});
|
|
675
|
-
|
|
676
|
-
if (data.choices && data.choices.length > 0) {
|
|
677
|
-
content = data.choices[0].message.content;
|
|
678
|
-
|
|
679
|
-
// Critical check for empty content
|
|
680
|
-
if (!content || content.trim().length === 0) {
|
|
681
|
-
logger.error(
|
|
682
|
-
`[OpenRouter] CRITICAL: API returned successful response but content is empty!`,
|
|
683
|
-
);
|
|
684
|
-
logger.error(`[OpenRouter] Response details:`, {
|
|
685
|
-
modelName,
|
|
686
|
-
promptLength: prompt.length,
|
|
687
|
-
responseStatus: response.status,
|
|
688
|
-
responseHeaders: Object.fromEntries(response.headers.entries()),
|
|
689
|
-
fullApiResponse: JSON.stringify(data, null, 2),
|
|
690
|
-
});
|
|
691
|
-
throw new Error(`OpenRouter API returned empty content for model ${modelName}`);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// Check for truncated responses
|
|
695
|
-
if (isTruncated) {
|
|
696
|
-
logger.warn(`[OpenRouter] WARNING: Response appears to be truncated!`);
|
|
697
|
-
logger.warn(`[OpenRouter] Truncation details:`, {
|
|
698
|
-
modelName,
|
|
699
|
-
finishReason,
|
|
700
|
-
contentLength: content.length,
|
|
701
|
-
maxTokensRequested:
|
|
702
|
-
getMaxTokensForReviewType(reviewType, options?.isConsolidation) || 'unlimited',
|
|
703
|
-
reviewType,
|
|
704
|
-
isConsolidation: options?.isConsolidation || false,
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
// For now, we'll continue with the truncated response but log it
|
|
708
|
-
// In the future, we could implement retry logic with shorter prompts
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
logger.debug(`Successfully generated review with OpenRouter ${modelName}`);
|
|
712
|
-
} else {
|
|
713
|
-
logger.error(`[OpenRouter] Invalid response format:`, JSON.stringify(data, null, 2));
|
|
714
|
-
throw new Error(`Invalid response format from OpenRouter ${modelName}`);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// Calculate cost information
|
|
718
|
-
try {
|
|
719
|
-
cost = getCostInfoFromText(prompt, content, `openrouter:${modelName}`);
|
|
720
|
-
} catch (error) {
|
|
721
|
-
logger.warn(
|
|
722
|
-
`Failed to calculate cost information: ${
|
|
723
|
-
error instanceof Error ? error.message : String(error)
|
|
724
|
-
}`,
|
|
725
|
-
);
|
|
726
|
-
}
|
|
727
|
-
} catch (error) {
|
|
728
|
-
throw new ApiError(
|
|
729
|
-
`Failed to generate consolidated review with OpenRouter ${modelName}: ${
|
|
730
|
-
error instanceof Error ? error.message : String(error)
|
|
731
|
-
}`,
|
|
732
|
-
);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// Try to parse the response as JSON with robust error recovery
|
|
736
|
-
let structuredData = null;
|
|
737
|
-
try {
|
|
738
|
-
// Try multiple strategies to extract JSON from the response
|
|
739
|
-
const jsonExtractionStrategies = [
|
|
740
|
-
// Strategy 1: Look for JSON in markdown code blocks
|
|
741
|
-
() => {
|
|
742
|
-
const patterns = [
|
|
743
|
-
/```(?:json)?\s*([\s\S]*?)\s*```/,
|
|
744
|
-
/```(?:typescript|javascript|ts|js)?\s*([\s\S]*?)\s*```/,
|
|
745
|
-
];
|
|
746
|
-
for (const pattern of patterns) {
|
|
747
|
-
const match = content.match(pattern);
|
|
748
|
-
if (match && match[1]) {
|
|
749
|
-
const extracted = match[1].trim();
|
|
750
|
-
if (extracted.startsWith('{') && extracted.endsWith('}')) {
|
|
751
|
-
return extracted;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
return null;
|
|
756
|
-
},
|
|
757
|
-
|
|
758
|
-
// Strategy 2: Find balanced JSON object
|
|
759
|
-
() => {
|
|
760
|
-
const startIdx = content.indexOf('{');
|
|
761
|
-
if (startIdx === -1) return null;
|
|
762
|
-
|
|
763
|
-
let depth = 0;
|
|
764
|
-
let inString = false;
|
|
765
|
-
let escapeNext = false;
|
|
766
|
-
|
|
767
|
-
for (let i = startIdx; i < content.length; i++) {
|
|
768
|
-
const char = content[i];
|
|
769
|
-
|
|
770
|
-
if (escapeNext) {
|
|
771
|
-
escapeNext = false;
|
|
772
|
-
continue;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
if (char === '\\') {
|
|
776
|
-
escapeNext = true;
|
|
777
|
-
continue;
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
if (char === '"' && !escapeNext) {
|
|
781
|
-
inString = !inString;
|
|
782
|
-
continue;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
if (!inString) {
|
|
786
|
-
if (char === '{') depth++;
|
|
787
|
-
else if (char === '}') {
|
|
788
|
-
depth--;
|
|
789
|
-
if (depth === 0) {
|
|
790
|
-
return content.substring(startIdx, i + 1);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// If we couldn't find balanced braces, try to fix unterminated strings
|
|
797
|
-
if (inString) {
|
|
798
|
-
// Add a closing quote and try to close the object
|
|
799
|
-
return content.substring(startIdx) + '"}';
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
return null;
|
|
803
|
-
},
|
|
804
|
-
|
|
805
|
-
// Strategy 3: Use the content as-is
|
|
806
|
-
() => content,
|
|
807
|
-
];
|
|
808
|
-
|
|
809
|
-
let jsonContent = null;
|
|
810
|
-
for (const strategy of jsonExtractionStrategies) {
|
|
811
|
-
try {
|
|
812
|
-
const extracted = strategy();
|
|
813
|
-
if (extracted) {
|
|
814
|
-
// Try to parse the extracted content
|
|
815
|
-
structuredData = JSON.parse(extracted);
|
|
816
|
-
|
|
817
|
-
// Validate that it has the expected structure
|
|
818
|
-
if (structuredData && typeof structuredData === 'object') {
|
|
819
|
-
jsonContent = extracted;
|
|
820
|
-
logger.debug('Successfully extracted and parsed JSON');
|
|
821
|
-
break;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
} catch (err) {
|
|
825
|
-
// Continue to next strategy
|
|
826
|
-
logger.debug(
|
|
827
|
-
`JSON extraction strategy failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
828
|
-
);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
// If we successfully parsed JSON, validate its structure
|
|
833
|
-
if (structuredData && !structuredData.summary && !Array.isArray(structuredData.issues)) {
|
|
834
|
-
logger.warn('Response is valid JSON but does not have the expected structure');
|
|
835
|
-
// Still keep the structured data as it might have partial information
|
|
836
|
-
}
|
|
837
|
-
} catch (parseError) {
|
|
838
|
-
logger.warn(
|
|
839
|
-
`Failed to parse response as JSON after all recovery attempts: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
|
|
840
|
-
);
|
|
841
|
-
// Keep the original response as content
|
|
842
|
-
structuredData = null;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// Return the review result
|
|
846
|
-
return {
|
|
847
|
-
content,
|
|
848
|
-
cost,
|
|
849
|
-
modelUsed: `openrouter:${modelName}`,
|
|
850
|
-
filePath: 'consolidated',
|
|
851
|
-
reviewType,
|
|
852
|
-
timestamp: new Date().toISOString(),
|
|
853
|
-
structuredData,
|
|
854
|
-
};
|
|
855
|
-
} catch (error) {
|
|
856
|
-
logger.error(
|
|
857
|
-
`Error generating consolidated review: ${
|
|
858
|
-
error instanceof Error ? error.message : String(error)
|
|
859
|
-
}`,
|
|
860
|
-
);
|
|
861
|
-
throw error;
|
|
862
|
-
}
|
|
863
|
-
}
|