Code Review MCP Server
by crazyrabbitLTC
Verified
/**
* @file LLM Service
* @version 0.1.0
*
* Service for interacting with LLMs using direct API calls
*/
import { LLMConfig, CodeReviewOptions, CodeReviewResult } from './types.js';
import fetch from 'node-fetch';
// Response types for different LLM providers
interface OpenAIResponse {
choices: Array<{
message: {
content: string;
};
}>;
}
interface AnthropicResponse {
content: Array<{
text: string;
}>;
}
interface GeminiResponse {
candidates: Array<{
content: {
parts: Array<{
text: string;
}>;
};
}>;
}
/**
* Service for interacting with LLMs via direct API calls
*/
export class LLMService {
private config: LLMConfig;
/**
* Creates a new LLMService
* @param config LLM configuration
*/
constructor(config: LLMConfig) {
this.config = config;
console.log(`LLM service initialized with provider ${config.provider} and model ${config.model}`);
}
/**
* Generates a review using the LLM
* @param prompt Prompt to send to the LLM
* @returns Generated review
*/
async generateReview(prompt: string): Promise<CodeReviewResult> {
try {
console.log('Sending code review request to LLM...');
// Ensure API key exists
if (!this.config.apiKey) {
throw new Error(`API key not provided for ${this.config.provider}`);
}
// Determine the API endpoint based on the provider
let endpoint: string;
let headers: Record<string, string> = {
'Content-Type': 'application/json'
};
let requestBody: any;
switch (this.config.provider) {
case 'OPEN_AI':
endpoint = 'https://api.openai.com/v1/chat/completions';
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
requestBody = {
model: this.config.model,
messages: [{ role: 'user', content: prompt }],
temperature: 0,
max_tokens: 4000,
response_format: { type: 'json_object' }
};
break;
case 'ANTHROPIC':
endpoint = 'https://api.anthropic.com/v1/messages';
headers['x-api-key'] = this.config.apiKey;
headers['anthropic-version'] = '2023-06-01';
requestBody = {
model: this.config.model,
messages: [{ role: 'user', content: prompt }],
temperature: 0,
max_tokens: 4000,
response_format: { type: 'json_object' }
};
break;
case 'GEMINI':
endpoint = `https://generativelanguage.googleapis.com/v1/models/${this.config.model}:generateContent?key=${this.config.apiKey}`;
requestBody = {
contents: [{ role: 'user', parts: [{ text: prompt }] }],
generationConfig: {
temperature: 0,
maxOutputTokens: 4000
}
};
break;
default:
throw new Error(`Unsupported LLM provider: ${this.config.provider}`);
}
// Make the API request
try {
const response = await fetch(endpoint, {
method: 'POST',
headers,
body: JSON.stringify(requestBody)
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`LLM API request failed: ${response.status} ${response.statusText} - ${errorText}`);
}
// Parse the response JSON with appropriate type
let responseText: string;
switch (this.config.provider) {
case 'OPEN_AI': {
const data = await response.json() as OpenAIResponse;
responseText = data.choices[0].message.content;
break;
}
case 'ANTHROPIC': {
const data = await response.json() as AnthropicResponse;
responseText = data.content[0].text;
break;
}
case 'GEMINI': {
const data = await response.json() as GeminiResponse;
responseText = data.candidates[0].content.parts[0].text;
break;
}
default:
throw new Error(`Unsupported LLM provider: ${this.config.provider}`);
}
// Parse the result
return this.parseReviewResponse(responseText);
} catch (error) {
console.error('Fetch error:', error);
throw new Error(`API request failed: ${(error as Error).message}`);
}
} catch (error) {
console.error('LLM request failed:', error);
throw new Error(`Failed to generate review: ${(error as Error).message}`);
}
}
/**
* Parses the LLM response into a structured format
* @param responseText LLM response text
* @returns Parsed review result
*/
private parseReviewResponse(responseText: string): CodeReviewResult {
try {
// Clean the response text - remove markdown code blocks if present
let cleanedResponse = responseText.trim();
// Handle responses wrapped in markdown code blocks
const jsonPattern = /```(?:json)?\s*([\s\S]*?)```/;
const match = cleanedResponse.match(jsonPattern);
if (match && match[1]) {
cleanedResponse = match[1].trim();
}
// Parse the JSON response
const parsedResponse = JSON.parse(cleanedResponse) as CodeReviewResult;
// Validate the response structure
if (!parsedResponse.summary ||
!Array.isArray(parsedResponse.issues) ||
!Array.isArray(parsedResponse.strengths) ||
!Array.isArray(parsedResponse.recommendations)) {
throw new Error('Invalid response structure from LLM');
}
return parsedResponse;
} catch (error) {
console.error('Failed to parse LLM response:', error);
console.error('Response text:', responseText);
throw new Error(`Failed to parse LLM response: ${(error as Error).message}`);
}
}
}