UNPKG

@kleosr/pe2-cli

Version:

🚀 KleoSr PE2-CLI: Convert raw prompts to PE2-optimized prompts using adaptive intelligence

216 lines (194 loc) • 9 kB
/** * OpenRouter client implementation * Provides access to multiple LLM providers through OpenRouter's unified API */ /** * OpenRouter client that implements the LLMClient interface * Uses OpenAI-compatible API format */ export class OpenRouterClient { constructor(options) { if (!options.apiKey || typeof options.apiKey !== 'string') { throw new Error('OpenRouter API key is required and must be a non-empty string'); } this.apiKey = options.apiKey; this.baseURL = options.baseURL || 'https://openrouter.ai/api/v1'; this.headers = { 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.apiKey}`, 'HTTP-Referer': options.httpReferer || 'https://pe2-cli-tool.local', 'X-Title': options.xTitle || 'KleoSr PE2-CLI Tool', ...options.customHeaders }; } chat = { completions: { create: async (options) => { // Validate required parameters if (!options.model || typeof options.model !== 'string') { throw new Error('OpenRouter model parameter is required and must be a string'); } if (!options.messages || !Array.isArray(options.messages) || options.messages.length === 0) { throw new Error('OpenRouter messages parameter is required and must be a non-empty array'); } // Validate message format for (const message of options.messages) { if (!message.role || !['system', 'user', 'assistant'].includes(message.role)) { throw new Error(`Invalid message role: ${message.role}. Must be 'system', 'user', or 'assistant'`); } if (!message.content || typeof message.content !== 'string') { throw new Error('Message content is required and must be a string'); } } // Prepare request body with OpenRouter-specific parameters const requestBody = { model: options.model, messages: options.messages, max_tokens: options.max_tokens || 2048, temperature: options.temperature !== undefined ? Math.max(0, Math.min(2, options.temperature)) : 0.7, top_p: options.top_p !== undefined ? Math.max(0, Math.min(1, options.top_p)) : 1, frequency_penalty: options.frequency_penalty !== undefined ? Math.max(-2, Math.min(2, options.frequency_penalty)) : 0, presence_penalty: options.presence_penalty !== undefined ? Math.max(-2, Math.min(2, options.presence_penalty)) : 0, stream: options.stream || false, ...(options.stop && { stop: options.stop }), ...(options.user && { user: options.user }) }; try { const response = await fetch(`${this.baseURL}/chat/completions`, { method: 'POST', headers: this.headers, body: JSON.stringify(requestBody) }); if (!response.ok) { await this.handleErrorResponse(response); } const data = await response.json(); // Validate response format if (!data.choices || !Array.isArray(data.choices) || data.choices.length === 0) { throw new Error('Invalid response format from OpenRouter'); } return { choices: data.choices.map((choice) => ({ message: { content: choice.message?.content || '', role: 'assistant' }, finish_reason: choice.finish_reason || 'stop' })), usage: data.usage || { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }, model: data.model || options.model, id: data.id, created: data.created }; } catch (error) { if (error instanceof Error && error.message.includes('fetch')) { throw new Error('OpenRouter network error. Please check your connection.'); } throw error; } } } }; /** * Handle error responses from OpenRouter API */ async handleErrorResponse(response) { let errorMessage = 'OpenRouter API error'; try { const errorData = await response.json(); errorMessage = errorData.error?.message || errorData.message || errorMessage; } catch { // If we can't parse the error response, use the status text errorMessage = response.statusText || errorMessage; } switch (response.status) { case 401: throw new Error('OpenRouter authentication failed. Please check your API key.'); case 429: const retryAfter = response.headers.get('retry-after'); const rateLimitMessage = retryAfter ? `OpenRouter rate limit exceeded. Retry after ${retryAfter} seconds.` : 'OpenRouter rate limit exceeded. Please try again later.'; throw new Error(rateLimitMessage); case 400: throw new Error(`OpenRouter request error: ${errorMessage}`); case 404: throw new Error(`OpenRouter model not found. Please check the model name.`); case 402: throw new Error('OpenRouter insufficient credits. Please check your account balance.'); case 403: throw new Error('OpenRouter access denied. Please check your API key permissions.'); case 413: throw new Error('OpenRouter request too large. Please reduce the input size.'); case 422: throw new Error(`OpenRouter validation error: ${errorMessage}`); case 500: case 502: case 503: case 504: throw new Error('OpenRouter server error. Please try again later.'); default: throw new Error(`OpenRouter API error (${response.status}): ${errorMessage}`); } } /** * Get available models from OpenRouter */ async getModels() { try { const response = await fetch(`${this.baseURL}/models`, { headers: { 'Authorization': `Bearer ${this.apiKey}`, 'HTTP-Referer': this.headers['HTTP-Referer'], 'X-Title': this.headers['X-Title'] } }); if (!response.ok) { throw new Error(`Failed to fetch models: ${response.statusText}`); } const data = await response.json(); return data.data || []; } catch (error) { throw new Error(`Error fetching OpenRouter models: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Get account information */ async getAccount() { try { const response = await fetch(`${this.baseURL}/auth/key`, { headers: { 'Authorization': `Bearer ${this.apiKey}`, 'HTTP-Referer': this.headers['HTTP-Referer'], 'X-Title': this.headers['X-Title'] } }); if (!response.ok) { throw new Error(`Failed to fetch account info: ${response.statusText}`); } return await response.json(); } catch (error) { throw new Error(`Error fetching OpenRouter account: ${error instanceof Error ? error.message : 'Unknown error'}`); } } } /** * Factory function to create OpenRouter client */ export function createOpenRouterClient(options) { return new OpenRouterClient(options); } /** * Helper function for backward compatibility with existing code */ export function createOpenAICompatibleClient(apiKey, baseURL) { return new OpenRouterClient({ apiKey, baseURL: baseURL || 'https://openrouter.ai/api/v1' }); }