UNPKG

@callmedayz/ai-prompt-toolkit

Version:

Professional AI prompt engineering toolkit with advanced template features, real-time dashboards, conditional logic, template inheritance, live monitoring, OpenRouter integration, and 310+ model support

341 lines 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.OpenRouterClient = void 0; const error_handling_1 = require("./error-handling"); const rate_limiting_1 = require("./rate-limiting"); /** * OpenRouter API client for making authenticated requests */ class OpenRouterClient { constructor(config, retryConfig, rateLimitConfig, quotaConfig) { this.config = { apiKey: config.apiKey, baseUrl: config.baseUrl || 'https://openrouter.ai/api/v1', timeout: config.timeout || 30000, retries: config.retries || 3 }; this.retryConfig = { ...error_handling_1.DEFAULT_RETRY_CONFIG, ...retryConfig }; this.circuitBreaker = new error_handling_1.CircuitBreaker(); // Initialize rate limiter if config provided if (rateLimitConfig) { this.rateLimiter = new rate_limiting_1.RateLimiter(rateLimitConfig); } // Initialize quota manager if config provided if (quotaConfig) { this.quotaManager = new rate_limiting_1.QuotaManager(quotaConfig); } if (!this.config.apiKey) { throw new error_handling_1.OpenRouterError('OpenRouter API key is required', 'authentication', undefined, false); } } /** * Make a completion request to OpenRouter */ async completion(request) { if (request.stream) { throw new Error('Use completionStream() for streaming requests'); } // Estimate tokens and cost for rate limiting const estimatedTokens = this.estimateTokens(request); const estimatedCost = this.estimateCost(request); const response = await this.makeRequest('/chat/completions', { method: 'POST', body: JSON.stringify(request) }, estimatedTokens, estimatedCost); // Record actual usage this.recordUsage(response.usage.total_tokens, this.calculateActualCost(response)); return response; } /** * Make a streaming completion request to OpenRouter */ async completionStream(request) { const streamRequest = { ...request, stream: true }; const url = `${this.config.baseUrl}/chat/completions`; const headers = { 'Authorization': `Bearer ${this.config.apiKey}`, 'Content-Type': 'application/json', 'HTTP-Referer': 'https://github.com/callmedayz/ai-prompt-toolkit', 'X-Title': 'AI Prompt Toolkit' }; const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(streamRequest) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`OpenRouter streaming error (${response.status}): ${errorText}`); } if (!response.body) { throw new Error('No response body for streaming request'); } return this.createStreamParser(response.body); } /** * Get token count for text using OpenRouter's completion API * This makes a minimal completion request to get accurate token counts */ async tokenize(request) { // Make a minimal completion request to get token count const completionRequest = { model: request.model, messages: [{ role: 'user', content: request.text }], max_tokens: 1, // Minimal completion to just get token count temperature: 0 }; try { const response = await this.completion(completionRequest); return { tokens: response.usage.prompt_tokens, token_ids: undefined // Not available through this method }; } catch (error) { throw new Error(`Tokenization failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Get available models from OpenRouter */ async getModels() { return this.makeRequest('/models', { method: 'GET' }); } /** * Get generation details including native token counts */ async getGeneration(id) { return this.makeRequest(`/generation?id=${id}`, { method: 'GET' }); } /** * Make an authenticated request to OpenRouter API with enhanced error handling */ async makeRequest(endpoint, options, estimatedTokens = 0, estimatedCost = 0) { // Check rate limits if (this.rateLimiter) { const rateLimitStatus = this.rateLimiter.checkRequest(estimatedTokens, estimatedCost); if (!rateLimitStatus.allowed) { throw new error_handling_1.OpenRouterError(rateLimitStatus.reason || 'Rate limit exceeded', 'rate_limit', 429, true, rateLimitStatus.retryAfter); } } // Check quota if (this.quotaManager) { const quotaStatus = this.quotaManager.checkQuota(estimatedCost); if (!quotaStatus.allowed) { throw new error_handling_1.OpenRouterError(quotaStatus.alert?.message || 'Quota exceeded', 'quota_exceeded', 402, false); } } return this.circuitBreaker.execute(async () => { return (0, error_handling_1.retryWithBackoff)(async () => { const url = `${this.config.baseUrl}${endpoint}`; const headers = { 'Authorization': `Bearer ${this.config.apiKey}`, 'Content-Type': 'application/json', 'HTTP-Referer': 'https://github.com/callmedayz/ai-prompt-toolkit', 'X-Title': 'AI Prompt Toolkit' }; const requestOptions = { ...options, headers: { ...headers, ...options.headers } }; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); try { const response = await fetch(url, { ...requestOptions, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { const errorText = await response.text(); throw (0, error_handling_1.parseError)(response, errorText); } return await response.json(); } catch (error) { clearTimeout(timeoutId); throw error; } }, this.retryConfig); }); } /** * Parse Server-Sent Events stream from OpenRouter */ createStreamParser(body) { const reader = body.getReader(); const decoder = new TextDecoder(); return new ReadableStream({ async start(controller) { let buffer = ''; try { while (true) { const { done, value } = await reader.read(); if (done) { break; } buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { const trimmed = line.trim(); // Skip empty lines and comments if (!trimmed || trimmed.startsWith(':')) { continue; } // Parse SSE format: "data: {...}" if (trimmed.startsWith('data: ')) { const data = trimmed.slice(6); // Check for end of stream if (data === '[DONE]') { controller.close(); return; } try { const parsed = JSON.parse(data); const content = parsed.choices?.[0]?.delta?.content; if (content) { controller.enqueue(content); } } catch (parseError) { console.warn('Failed to parse streaming data:', data); } } } } } catch (error) { controller.error(error); } finally { reader.releaseLock(); } controller.close(); } }); } /** * Test the API connection and authentication */ async testConnection() { try { await this.getModels(); return true; } catch (error) { return false; } } /** * Get API key from environment or throw error */ static getApiKeyFromEnv() { const apiKey = process.env.OPENROUTER_API_KEY; if (!apiKey) { throw new Error('OPENROUTER_API_KEY environment variable is required. ' + 'Get your API key from https://openrouter.ai/keys'); } return apiKey; } /** * Get circuit breaker status */ getCircuitBreakerStatus() { return this.circuitBreaker.getState(); } /** * Update retry configuration */ updateRetryConfig(config) { this.retryConfig = { ...this.retryConfig, ...config }; } /** * Get current retry configuration */ getRetryConfig() { return { ...this.retryConfig }; } /** * Estimate tokens for a request (rough estimation) */ estimateTokens(request) { const text = request.messages.map(m => m.content).join(' '); return Math.ceil(text.length / 4); // Rough estimation: 1 token ≈ 4 characters } /** * Estimate cost for a request (very rough estimation) */ estimateCost(request) { const tokens = this.estimateTokens(request); // Very rough cost estimation - would need model-specific pricing return tokens * 0.00002; // Rough estimate for GPT-3.5-turbo pricing } /** * Calculate actual cost from response */ calculateActualCost(response) { // This would need model-specific pricing data // For now, use rough estimation return response.usage.total_tokens * 0.00002; } /** * Record usage in rate limiter and quota manager */ recordUsage(tokens, cost) { if (this.rateLimiter) { this.rateLimiter.recordUsage(tokens, cost); } if (this.quotaManager) { this.quotaManager.recordSpending(cost); } } /** * Get rate limiting status */ getRateLimitStatus() { return this.rateLimiter?.getUsage(); } /** * Get quota status */ getQuotaStatus() { return this.quotaManager?.getUsage(); } /** * Get quota alerts */ getQuotaAlerts() { return this.quotaManager?.getAlerts() || []; } /** * Enable rate limiting */ enableRateLimiting(config) { this.rateLimiter = new rate_limiting_1.RateLimiter(config); } /** * Enable quota management */ enableQuotaManagement(config) { this.quotaManager = new rate_limiting_1.QuotaManager(config); } /** * Create a client instance using environment variables */ static fromEnv(config, retryConfig, rateLimitConfig, quotaConfig) { return new OpenRouterClient({ apiKey: this.getApiKeyFromEnv(), ...config }, retryConfig, rateLimitConfig, quotaConfig); } } exports.OpenRouterClient = OpenRouterClient; //# sourceMappingURL=openrouter-client.js.map