UNPKG

blackboxify

Version:

BlackboxAI API client with OpenAI-compatible interface, streaming support, multiple auth accounts, and token pricing

264 lines (230 loc) 7.72 kB
import axios from 'axios'; import { BlackboxError } from './errors.js'; const VALIDATION_TOKEN = "00f37b34-a166-4efb-bce5-1312d87f2f94"; const DEFAULT_API_ENDPOINT = 'https://www.blackbox.ai/api/chat'; const RATE_LIMIT_MESSAGES = [ "Your hourly rate limit is reached", "You have reached your request limit for the hour." ]; function isRateLimitResponse(content) { if (!content) return false; return RATE_LIMIT_MESSAGES.some(msg => content.toLowerCase().includes(msg.toLowerCase()) ); } function createRequestData(params, auth) { const { messages, model, max_tokens, user } = params; return { messages, userSelectedModel: model, agentMode: { mode: true, id: model, name: model.split('/').pop() }, id: user || "BlackboxAIUser", trendingAgentMode: {}, userSystemPrompt: messages.find(msg => msg.role === 'system')?.content || null, maxTokens: max_tokens || 4096, validated: VALIDATION_TOKEN, session: { user: { email: auth.email } }, subscriptionCache: { customerId: auth?.customer_id }, isPremium: true, webSearchModeOption: { autoMode: false, webMode: false, offlineMode: true }, workspaceId: "" }; } function createHeaders(isStream = false) { return { 'accept': isStream ? 'text/event-stream' : '*/*', 'accept-encoding': 'identity', 'accept-language': 'en-US,en;q=0.9', 'cache-control': 'no-cache', 'content-type': 'application/json', 'origin': 'https://www.blackbox.ai', 'pragma': 'no-cache', 'referer': 'https://www.blackbox.ai/', 'sec-ch-ua': '"Not(A:Brand";v="99", "Google Chrome";v="133", "Chromium";v="133"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-origin', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36' }; } function extractAIResponse(responseData) { if (!responseData) return "No response data received"; if (typeof responseData === 'string') return responseData; if (responseData.response) return responseData.response; if (responseData.generations?.length > 0) return responseData.generations[0].text; if (responseData.choices?.length > 0) return responseData.choices[0].message.content; if (responseData.text) return responseData.text; return "Response received but in unexpected format"; } function formatResponse(aiResponse, model) { return { id: `chatcmpl-${Date.now()}`, object: "chat.completion", created: Math.floor(Date.now() / 1000), model: model, choices: [ { index: 0, message: { role: "assistant", content: aiResponse, }, logprobs: null, finish_reason: "stop" } ], usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 } }; } async function makeRequest(params) { const { authManager, ...requestParams } = params; return authManager.tryWithRetry(async (auth) => { try { const requestData = createRequestData(requestParams, auth); const headers = createHeaders(); const response = await axios.post(DEFAULT_API_ENDPOINT, requestData, { headers }); const aiResponse = extractAIResponse(response.data); // Check if the response indicates rate limiting if (isRateLimitResponse(aiResponse)) { throw new BlackboxError( 'Rate limit reached', 'Account rate limit reached. Switching to next account...', 429 ); } // For non-rate-limit responses, return formatted response const formattedResponse = formatResponse(aiResponse, requestParams.model); // If this is a rate limit message but not detected by isRateLimitResponse if (isRateLimitResponse(formattedResponse.choices[0].message.content)) { throw new BlackboxError( 'Rate limit reached', 'Account rate limit reached. Switching to next account...', 429 ); } return formattedResponse; } catch (error) { throw new BlackboxError( 'API request failed', error.response?.data || error.message, error.response?.status || error.statusCode ); } }); } async function* createStream(params) { const { authManager, ...requestParams } = params; const response = await authManager.tryWithRetry(async (auth) => { const requestData = createRequestData(requestParams, auth); const headers = createHeaders(true); try { const response = await axios.post(DEFAULT_API_ENDPOINT, requestData, { headers, responseType: 'stream' }); return response; } catch (error) { throw new BlackboxError( 'Stream request failed', error.response?.data || error.message, error.response?.status ); } }); let buffer = ''; let rateLimitDetected = false; let totalPromptTokens = 0; let totalCompletionTokens = 0; for await (const chunk of response.data) { const text = chunk.toString(); buffer += text; const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.trim()) { if (isRateLimitResponse(line)) { rateLimitDetected = true; throw new BlackboxError( 'Rate limit reached', 'Account rate limit reached', 429 ); } // Estimate tokens for this chunk content const chunkTokens = line.trim().split(/\s+/).length; totalCompletionTokens += chunkTokens; // Usage object with cumulative tokens and cost const usage = { prompt_tokens: requestParams.max_tokens || 0, completion_tokens: totalCompletionTokens, total_tokens: (requestParams.max_tokens || 0) + totalCompletionTokens, cost: 0 // Cost calculation can be added if pricing info is available }; yield { id: `chatcmpl-${Date.now()}`, object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model: requestParams.model, choices: [{ index: 0, delta: { content: line }, finish_reason: null }], usage }; } } } if (buffer.trim()) { if (isRateLimitResponse(buffer)) { throw new BlackboxError( 'Rate limit reached', 'Account rate limit reached', 429 ); } if (!rateLimitDetected) { const chunkTokens = buffer.trim().split(/\s+/).length; totalCompletionTokens += chunkTokens; const usage = { prompt_tokens: requestParams.max_tokens || 0, completion_tokens: totalCompletionTokens, total_tokens: (requestParams.max_tokens || 0) + totalCompletionTokens, cost: 0 }; yield { id: `chatcmpl-${Date.now()}`, object: "chat.completion.chunk", created: Math.floor(Date.now() / 1000), model: requestParams.model, choices: [{ index: 0, delta: { content: buffer.trim() }, finish_reason: "stop" }], usage }; } } } export { makeRequest, createStream };