Spaces:
Paused
Paused
| ; | |
| // Node 20 includes native fetch support | |
| var __importDefault = (this && this.__importDefault) || function (mod) { | |
| return (mod && mod.__esModule) ? mod : { "default": mod }; | |
| }; | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.Bot = void 0; | |
| // AI Client for Groq with Failover Support + Circuit Breaker | |
| // Fallback shims for Probot runtime | |
| /* eslint-disable no-console */ | |
| const chatgpt_1 = require("chatgpt"); | |
| const p_retry_1 = __importDefault(require("p-retry")); | |
| const opossum_1 = __importDefault(require("opossum")); | |
| const options_1 = require("./options"); | |
| const limits_1 = require("./limits"); | |
| const pino_1 = __importDefault(require("pino")); | |
| const logger = (0, pino_1.default)({ level: process.env.LOG_LEVEL || 'info' }); | |
| // Circuit breaker configuration | |
| const CIRCUIT_BREAKER_OPTIONS = { | |
| timeout: 60000, // 60 seconds | |
| errorThresholdPercentage: 50, | |
| resetTimeout: 30000, | |
| volumeThreshold: 5, | |
| }; | |
| const logInfo = (msg) => logger.info(msg); | |
| const logWarning = (msg) => logger.warn(msg); | |
| const logError = (msg) => logger.error(msg); | |
| const setFailed = (msg) => { | |
| logger.error(msg); | |
| process.exit(1); | |
| }; | |
| class Bot { | |
| api = null; | |
| options; | |
| aiOptions; | |
| currentModel; | |
| fallbackModels; | |
| circuitBreaker = null; | |
| constructor(options, aiOptions) { | |
| this.options = options; | |
| this.aiOptions = aiOptions; | |
| this.currentModel = aiOptions.model; | |
| // Setup fallback chain based on tier | |
| if (options_1.Options.HEAVY_MODELS.includes(this.currentModel)) { | |
| this.fallbackModels = [...options_1.Options.HEAVY_MODELS]; | |
| } | |
| else { | |
| this.fallbackModels = [...options_1.Options.LIGHT_MODELS]; | |
| } | |
| // Remove current model from fallbacks to avoid immediate repeat | |
| this.fallbackModels = this.fallbackModels.filter(m => m !== this.currentModel); | |
| this.initClient(); | |
| this.initCircuitBreaker(); | |
| } | |
| initCircuitBreaker() { | |
| if (!this.api) | |
| return; | |
| // Wrap the sendMessage method with circuit breaker | |
| this.circuitBreaker = new opossum_1.default(async ({ message, opts }) => { | |
| return this.api.sendMessage(message, opts); | |
| }, CIRCUIT_BREAKER_OPTIONS); | |
| // Circuit breaker event handlers | |
| this.circuitBreaker.on('open', () => { | |
| logWarning(`Circuit breaker opened for model: ${this.currentModel}`); | |
| }); | |
| this.circuitBreaker.on('halfOpen', () => { | |
| logInfo(`Circuit breaker half-open for model: ${this.currentModel}`); | |
| }); | |
| this.circuitBreaker.on('close', () => { | |
| logInfo(`Circuit breaker closed for model: ${this.currentModel}`); | |
| }); | |
| this.circuitBreaker.on('fallback', (_result) => { | |
| logWarning(`Circuit breaker fallback executed for model: ${this.currentModel}`); | |
| }); | |
| } | |
| initClient() { | |
| const apiKey = process.env.GROQ_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} | |
| Knowledge cutoff: ${limits.knowledgeCutOff} | |
| Current date: ${currentDate} | |
| IMPORTANT: Entire response must be in ISO code: ${this.options.language} | |
| `; | |
| 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: limits.responseTokens, | |
| completionParams: { | |
| temperature: this.options.modelTemperature, | |
| model: this.currentModel | |
| } | |
| }); | |
| logInfo(`Initialized AI client with model: ${this.currentModel}`); | |
| } | |
| else { | |
| throw new Error("Missing 'GROQ_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) { | |
| logWarning(`AI communication error: ${e.message}`); | |
| } | |
| 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 { | |
| // Check if circuit breaker is open | |
| if (this.circuitBreaker?.opened) { | |
| logWarning(`Circuit breaker is OPEN for ${this.currentModel}, attempting fallback...`); | |
| if (this.fallbackModels.length > 0) { | |
| const nextModel = this.fallbackModels.shift(); | |
| if (nextModel) { | |
| this.currentModel = nextModel; | |
| this.initClient(); | |
| this.initCircuitBreaker(); | |
| // Recursive retry with new model | |
| return this.chat_(message, ids); | |
| } | |
| } | |
| throw new Error(`Circuit breaker open and no fallback models available for ${this.currentModel}`); | |
| } | |
| const result = await (0, p_retry_1.default)(async () => { | |
| if (!this.circuitBreaker) { | |
| throw new Error('Circuit breaker not initialized'); | |
| } | |
| try { | |
| const messageResult = await this.circuitBreaker.fire({ message, opts }); | |
| return messageResult; | |
| } | |
| catch (e) { | |
| // Check if circuit breaker opened | |
| if (this.circuitBreaker.opened && this.fallbackModels.length > 0) { | |
| const nextModel = this.fallbackModels.shift(); | |
| if (nextModel) { | |
| logWarning(`Circuit breaker opened for ${this.currentModel}. Switching to fallback: ${nextModel}`); | |
| this.currentModel = nextModel; | |
| this.initClient(); | |
| this.initCircuitBreaker(); | |
| // Throw to trigger retry with new model | |
| throw new Error(`Model switched to ${nextModel}, retrying...`); | |
| } | |
| } | |
| // Handle rate limiting / context size | |
| if ((e?.status === 429 || e?.status === 413) && this.fallbackModels.length > 0) { | |
| const nextModel = this.fallbackModels.shift(); | |
| if (nextModel) { | |
| logWarning(`Rate limit or context size error (${e.status}) hit for ${this.currentModel}. Switching to fallback: ${nextModel}`); | |
| this.currentModel = nextModel; | |
| this.initClient(); | |
| this.initCircuitBreaker(); | |
| throw new Error(`Model switched to ${nextModel}, retrying...`); | |
| } | |
| } | |
| throw e; | |
| } | |
| }, { | |
| retries: this.options.retries, | |
| minTimeout: 20000, // 20s delay to allow TPM refresh | |
| onFailedAttempt: (err) => { | |
| logInfo(`Attempt ${err.attemptNumber} failed. ${err.retriesLeft} retries left. Error: ${err.message}`); | |
| } | |
| }); | |
| response = result; | |
| } | |
| catch (e) { | |
| if (e instanceof chatgpt_1.ChatGPTError) { | |
| logWarning(`AI Error after retries: ${e.message} (Last model used: ${this.currentModel})`); | |
| } | |
| else if (e instanceof Error) { | |
| logError(`Failed to get AI response: ${e.message}`); | |
| } | |
| } | |
| const end = Date.now(); | |
| logInfo(`AI response time: ${end - start} ms`); | |
| } | |
| 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; | |
| //# sourceMappingURL=bot.js.map |