Mentor MCP Server
by cyanheads
Verified
- mentor-mcp-server
- src
- api
- deepseek
import OpenAI from 'openai';
import type { LLMResponse, ChatMessage } from '../../types/index.js';
import { config } from '../../config.js';
import { sanitizeInput } from '../../utils/prompt.js';
/**
* Rate limiter implementation using token bucket algorithm
*/
class RateLimiter {
private tokens: number;
private readonly maxTokens: number;
private readonly refillRate: number;
private lastRefill: number;
constructor(maxTokens: number = 50, refillRate: number = 10) {
this.tokens = maxTokens;
this.maxTokens = maxTokens;
this.refillRate = refillRate; // tokens per second
this.lastRefill = Date.now();
}
private refillTokens(): void {
const now = Date.now();
const timePassed = (now - this.lastRefill) / 1000; // convert to seconds
const tokensToAdd = Math.floor(timePassed * this.refillRate);
this.tokens = Math.min(
this.maxTokens,
this.tokens + tokensToAdd
);
this.lastRefill = now;
}
public tryConsume(): boolean {
this.refillTokens();
if (this.tokens >= 1) {
this.tokens -= 1;
return true;
}
return false;
}
}
/**
* Deepseek API client class using OpenAI SDK with deepseek-reasoner model
*/
class DeepseekClient {
private readonly client: OpenAI;
private readonly rateLimiter: RateLimiter;
constructor() {
this.client = new OpenAI({
baseURL: config.api.baseUrl || 'https://api.deepseek.com',
apiKey: config.api.apiKey,
defaultQuery: { model: 'deepseek-reasoner' }
});
this.rateLimiter = new RateLimiter(
50, // max 50 requests
10 // refill 10 tokens per second
);
}
/**
* Makes a call to the Deepseek API with exponential backoff retry
*/
private async retryWithExponentialBackoff<T>(
operation: () => Promise<T>,
maxRetries: number = config.api.maxRetries,
baseDelay: number = 1000
): Promise<T> {
let lastError: Error | null = null;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error as Error;
if (error instanceof OpenAI.APIError && error.status === 429) {
const delay = baseDelay * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
throw lastError || new Error('Max retries exceeded');
}
/**
* Makes a call to the Deepseek API using the reasoner model
*/
public async makeApiCall(
prompt: string,
systemPrompt: string = 'You are a helpful AI assistant.',
previousMessages: ChatMessage[] = []
): Promise<LLMResponse> {
// Check rate limit
if (!this.rateLimiter.tryConsume()) {
return {
text: '',
isError: true,
errorMessage: 'Rate limit exceeded. Please try again later.'
};
}
try {
// Sanitize inputs
const sanitizedPrompt = sanitizeInput(prompt);
const sanitizedSystemPrompt = sanitizeInput(systemPrompt);
// Prepare messages, filtering out any previous reasoning_content
const messages = [
{ role: 'system' as const, content: sanitizedSystemPrompt },
...previousMessages.map(msg => ({
role: msg.role,
content: msg.content
})),
{ role: 'user' as const, content: sanitizedPrompt }
];
const response = await this.retryWithExponentialBackoff(async () => {
const completion = await this.client.chat.completions.create({
model: 'deepseek-reasoner',
messages,
max_tokens: config.api.maxTokens || 4096
});
return completion;
});
// Extract both the reasoning and final content
const reasoningContent = (response.choices[0]?.message as any)?.reasoning_content || '';
const finalContent = response.choices[0]?.message?.content || '';
return {
text: finalContent,
reasoning: reasoningContent,
isError: false,
};
} catch (error) {
console.error('Deepseek API error:', error);
let errorMessage = 'Unknown error occurred';
if (error instanceof OpenAI.APIError) {
errorMessage = error.message;
} else if (error instanceof Error) {
errorMessage = error.message;
}
return {
text: '',
isError: true,
errorMessage
};
}
}
/**
* Checks if a request can be made (for external rate limit checking)
*/
public checkRateLimit(): boolean {
return this.rateLimiter.tryConsume();
}
}
// Export a singleton instance
export const deepseekClient = new DeepseekClient();
// Export the main interface functions
export const makeDeepseekAPICall = (
prompt: string,
systemPrompt?: string,
previousMessages: ChatMessage[] = []
) => deepseekClient.makeApiCall(prompt, systemPrompt, previousMessages);
export const checkRateLimit = () => deepseekClient.checkRateLimit();