| "use strict"; |
| |
| var __importDefault = (this && this.__importDefault) || function (mod) { |
| return (mod && mod.__esModule) ? mod : { "default": mod }; |
| }; |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.Bot = exports.AllModelsFailedError = void 0; |
| |
| |
| const info = console.log; |
| const warning = console.warn; |
| const error = console.error; |
| const debug = process.env.LOG_LEVEL === 'debug' ? console.log.bind(console) : () => { }; |
| const setFailed = (msg) => { |
| |
| throw new Error(msg); |
| }; |
| const chatgpt_1 = require("chatgpt"); |
| const p_retry_1 = __importDefault(require("p-retry")); |
| const options_1 = require("./options"); |
| const limits_1 = require("./limits"); |
| const tokenizer_1 = require("./tokenizer"); |
| const utils_1 = require("./utils"); |
| const async_mutex_1 = require("async-mutex"); |
| class TokenThrottler { |
| static usage = []; |
| static WINDOW_MS = 60000; |
| static mutex = new async_mutex_1.Mutex(); |
| |
| |
| static get LIMIT() { |
| return 1000000; |
| } |
| static async throttle(estimatedTokens) { |
| |
| await this.mutex.runExclusive(async () => { |
| const now = Date.now(); |
| this.usage = this.usage.filter(u => now - u.timestamp < this.WINDOW_MS); |
| const currentTokens = this.usage.reduce((sum, u) => sum + u.tokens, 0); |
| const limit = this.LIMIT; |
| if (currentTokens + estimatedTokens > limit) { |
| |
| const oldestTimestamp = this.usage.length > 0 ? this.usage[0].timestamp : now; |
| const waitTime = this.WINDOW_MS - (now - oldestTimestamp) + 1000; |
| info(`TPM Limit approaching (${currentTokens}/${limit} tokens used). Throttling for ${Math.round(waitTime / 1000)}s...`); |
| |
| this.mutex.release(); |
| await (0, utils_1.sleep)(Math.max(waitTime, 1000)); |
| |
| |
| await this.throttle(estimatedTokens); |
| return; |
| } |
| this.usage.push({ timestamp: Date.now(), tokens: estimatedTokens }); |
| }); |
| } |
| } |
| class AllModelsFailedError extends Error { |
| constructor() { |
| super('All AI models failed after exhausting fallback chain'); |
| this.name = 'AllModelsFailedError'; |
| } |
| } |
| exports.AllModelsFailedError = AllModelsFailedError; |
| class Bot { |
| api = null; |
| options; |
| aiOptions; |
| currentModel; |
| fallbackModels; |
| constructor(options, aiOptions, fallbackModels) { |
| this.options = options; |
| this.aiOptions = aiOptions; |
| this.currentModel = aiOptions.model; |
| |
| if (fallbackModels) { |
| this.fallbackModels = fallbackModels.filter(m => m !== this.currentModel); |
| } |
| else if (options_1.Options.FREE_TIER_MODELS.includes(this.currentModel)) { |
| this.fallbackModels = options_1.Options.FREE_TIER_MODELS.filter(m => m !== this.currentModel); |
| } |
| else if (options_1.Options.HEAVY_MODELS.includes(this.currentModel)) { |
| this.fallbackModels = options_1.Options.HEAVY_MODELS.filter(m => m !== this.currentModel); |
| } |
| else { |
| this.fallbackModels = options_1.Options.LIGHT_MODELS.filter(m => m !== this.currentModel); |
| } |
| this.initClient(); |
| } |
| getCurrentModel() { |
| return this.currentModel; |
| } |
| initClient() { |
| const apiKey = process.env.OPENROUTER_API_KEY || process.env.AI_API_KEY; |
| if (apiKey) { |
| const currentDate = new Date().toISOString().split('T')[0]; |
| const limits = new limits_1.TokenLimits(this.currentModel); |
| const systemMessage = `${this.options.systemMessage}
|
| Cutoff: ${limits.knowledgeCutOff}
|
| Date: ${currentDate}
|
| Language: ${this.options.language}`; |
| const completionParams = { |
| temperature: this.options.modelTemperature, |
| model: this.currentModel |
| }; |
| |
| if (this.currentModel === 'openai/gpt-oss-120b') { |
| completionParams.provider = { order: ['deallm', 'deepinfra'] }; |
| } |
| |
| if (this.currentModel === 'openai/gpt-oss-120b:free') { |
| completionParams.provider = { order: ['deepinfra'] }; |
| } |
| |
| |
| if (this.currentModel === 'qwen/qwen3-235b-a22b-2507') { |
| completionParams.provider = { order: ['deepinfra', 'wandb'] }; |
| } |
| this.api = new chatgpt_1.ChatGPTAPI({ |
| apiBaseUrl: this.options.apiBaseUrl, |
| systemMessage, |
| apiKey, |
| apiOrg: process.env.AI_API_ORG || undefined, |
| debug: this.options.debug, |
| maxModelTokens: limits.maxTokens, |
| maxResponseTokens: Math.min(1800, limits.responseTokens), |
| completionParams |
| }); |
| info(`Initialized AI client with model: ${this.currentModel}`); |
| } |
| else { |
| throw new Error("Missing 'OPENROUTER_API_KEY' or 'AI_API_KEY'"); |
| } |
| } |
| chat = async (message, ids) => { |
| let res = ['', {}]; |
| try { |
| res = await this.chat_(message, ids); |
| return res; |
| } |
| catch (e) { |
| if (e instanceof chatgpt_1.ChatGPTError) { |
| warning(`AI communication error: ${e.message}`); |
| } |
| if (e instanceof AllModelsFailedError) { |
| throw e; |
| } |
| return res; |
| } |
| }; |
| chat_ = async (message, ids) => { |
| const start = Date.now(); |
| if (!message) |
| return ['', {}]; |
| let response; |
| if (this.api != null) { |
| const opts = { |
| timeoutMs: this.options.timeoutMS |
| }; |
| if (ids.parentMessageId) { |
| opts.parentMessageId = ids.parentMessageId; |
| } |
| try { |
| |
| const limits = new limits_1.TokenLimits(this.currentModel); |
| const estimatedTokens = (0, tokenizer_1.getTokenCount)(message) + limits.responseTokens; |
| await TokenThrottler.throttle(estimatedTokens); |
| |
| const messageSize = message.length; |
| debug('MESSAGE_SIZE_CHARS', messageSize); |
| debug('MESSAGE_SIZE_KB', messageSize / 1024); |
| debug('HAS_PARENT_MESSAGE_ID', !!opts.parentMessageId); |
| debug('HAS_CONVERSATION_ID', !!ids.conversationId); |
| debug('MESSAGE_PREVIEW', message.substring(0, 500)); |
| |
| const generatedPatterns = [ |
| 'node_modules/', |
| 'package-lock.json', |
| 'yarn.lock', |
| 'pnpm-lock.yaml', |
| '.next/', |
| 'dist/', |
| 'build/', |
| '.git/' |
| ]; |
| const hasGeneratedContent = generatedPatterns.some(pattern => message.includes(pattern)); |
| if (hasGeneratedContent) { |
| console.warn('WARNING: Message may contain generated file content - upstream filtering should have removed this'); |
| for (const pattern of generatedPatterns) { |
| if (message.includes(pattern)) { |
| console.warn(` Found pattern: ${pattern}`); |
| } |
| } |
| } |
| |
| const MAX_PAYLOAD_CHARS = 120000; |
| if (messageSize > MAX_PAYLOAD_CHARS) { |
| error(`PAYLOAD_TOO_LARGE_PREVENTED: Message is ${messageSize} chars, limit is ${MAX_PAYLOAD_CHARS}`); |
| throw new Error(`PAYLOAD_TOO_LARGE_PREVENTED: Message is ${messageSize} chars, limit is ${MAX_PAYLOAD_CHARS}`); |
| } |
| const controller = new AbortController(); |
| const timeoutId = setTimeout(() => controller.abort(), this.options.timeoutMS); |
| try { |
| response = await (0, p_retry_1.default)(async () => { |
| if (controller.signal.aborted) |
| throw new Error('Request timed out'); |
| try { |
| return await this.api.sendMessage(message, opts); |
| } |
| catch (e) { |
| |
| if (e?.status === 413 || |
| e?.message?.includes('Request Entity Too Large')) { |
| error(`413 Payload Too Large - NOT retrying. Message size: ${messageSize} chars`); |
| throw e; |
| } |
| |
| if (this.fallbackModels.length > 0) { |
| const nextModel = this.fallbackModels.shift(); |
| if (nextModel) { |
| warning(`Model ${this.currentModel} failed (${e?.status || e?.message}). Switching to fallback: ${nextModel}`); |
| this.currentModel = nextModel; |
| this.initClient(); |
| delete opts.parentMessageId; |
| throw new AllModelsFailedError(); |
| } |
| } |
| throw e; |
| } |
| }, { |
| retries: this.options.retries, |
| onFailedAttempt: error => { |
| info(`Attempt ${error.attemptNumber} failed. ${error.retriesLeft} retries left.`); |
| } |
| }); |
| } |
| finally { |
| clearTimeout(timeoutId); |
| } |
| } |
| catch (e) { |
| if (e instanceof chatgpt_1.ChatGPTError) { |
| info(`AI Error: ${e.message} (Last model used: ${this.currentModel})`); |
| } |
| else if (e instanceof Error) { |
| error(`Non-AI error in chat: ${e.message} (Last model used: ${this.currentModel})`); |
| } |
| } |
| const end = Date.now(); |
| info(`AI response time: ${end - start} ms`); |
| |
| if (response != null) { |
| const inputTokens = (0, tokenizer_1.getTokenCount)(message); |
| const outputTokens = (0, tokenizer_1.getTokenCount)(response.text); |
| const totalTokens = inputTokens + outputTokens; |
| info(`Token usage: ${inputTokens}+${outputTokens}=${totalTokens} tokens (${this.currentModel})`); |
| } |
| } |
| else { |
| setFailed('AI client is not initialized'); |
| } |
| let responseText = ''; |
| if (response != null) { |
| responseText = response.text; |
| } |
| const newIds = { |
| parentMessageId: response?.id, |
| conversationId: response?.conversationId |
| }; |
| return [responseText, newIds]; |
| }; |
| } |
| exports.Bot = Bot; |
| |