blackboxify
Version:
BlackboxAI API client with OpenAI-compatible interface, streaming support, multiple auth accounts, and token pricing
264 lines (230 loc) • 7.72 kB
JavaScript
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 };