freegate 0.6.14 → 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/bandit.js CHANGED
@@ -69,4 +69,11 @@ function recordOutcome(bucketPriors, key, success) {
69
69
  else bucketPriors[key].b += 1;
70
70
  }
71
71
 
72
- module.exports = { bucket, sampleBeta, pick, recordOutcome, BUCKET_THRESHOLDS };
72
+ // Временные лимиты (429/403/402/401) это НЕ качество провайдера, а временное
73
+ // состояние (лимит запросов, квота, баланс). Bandit не должен наказывать за них.
74
+ const TRANSIENT_LIMITS = new Set([401, 402, 403, 408, 429]);
75
+ function isTransientLimit(status) {
76
+ return typeof status === 'number' && TRANSIENT_LIMITS.has(status);
77
+ }
78
+
79
+ module.exports = { bucket, sampleBeta, pick, recordOutcome, BUCKET_THRESHOLDS, isTransientLimit };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freegate",
3
- "version": "0.6.14",
3
+ "version": "0.6.15",
4
4
  "description": "Free multi-provider LLM gateway with automatic failover. One OpenAI-compatible endpoint routes to 25 free models (Groq, Mistral, Gemini, NIM, OpenRouter, ZAI, Cerebras, DeepSeek). Never pay for LLMs.",
5
5
  "license": "MIT",
6
6
  "bin": {
package/server.js CHANGED
@@ -10,7 +10,7 @@ const { handleDashboard } = require('./lib/dashboard');
10
10
  const { acquire, stats: poolStats } = require('./lib/pool');
11
11
  const { stripThink, cleanDelta, cleanMessage, fixReasoningMessage, isTooShort, MIN_ANSWER_LEN } = require('./lib/clean');
12
12
  const { classifyComplexity, maybeUpgradeTier, classifyVisionComplexity } = require('./lib/routing');
13
- const { bucket, pick: banditPick } = require('./lib/bandit');
13
+ const { bucket, pick: banditPick, isTransientLimit } = require('./lib/bandit');
14
14
  const logger = require('./lib/logger');
15
15
 
16
16
  // Load persisted state
@@ -441,7 +441,7 @@ async function handleChatCompletion(req, res, body) {
441
441
  });
442
442
  result.stream.on('error', (err) => {
443
443
  logger.error('Stream error', { key, error: err.message });
444
- recordBandit(complexityBucket, key, false);
444
+ if (!isTransientLimit(err.statusCode)) recordBandit(complexityBucket, key, false);
445
445
  res.end();
446
446
  });
447
447
  result.stream.pipe(cleaner).pipe(res);
@@ -527,7 +527,7 @@ async function handleChatCompletion(req, res, body) {
527
527
  });
528
528
  result.stream.on('error', (err) => {
529
529
  logger.error('Stream error', { key, error: err.message });
530
- recordBandit(complexityBucket, key, false);
530
+ if (!isTransientLimit(err.statusCode)) recordBandit(complexityBucket, key, false);
531
531
  res.end();
532
532
  });
533
533
  return;
@@ -551,7 +551,8 @@ async function handleChatCompletion(req, res, body) {
551
551
  const statusCode = err.statusCode || 502;
552
552
  errors.push(err.message);
553
553
  recordRequest(key, false, err.message);
554
- recordBandit(complexityBucket, key, false);
554
+ // Временные лимиты (429/403/402) не наказываем провайдера в bandit.
555
+ if (!isTransientLimit(statusCode)) recordBandit(complexityBucket, key, false);
555
556
  recordRecent({ model: requestedModel, provider: key, status: statusCode, latency: 0, cached: false });
556
557
  initHealth(key);
557
558
  // Do NOT flip provider to 'error' on a single failed request — transient
@@ -672,14 +673,14 @@ async function handleChatCompletion(req, res, body) {
672
673
  });
673
674
  result.stream.on('error', (err) => {
674
675
  logger.error('Stream error (retry)', { key, error: err.message });
675
- recordBandit(complexityBucket, key, false);
676
+ if (!isTransientLimit(err.statusCode)) recordBandit(complexityBucket, key, false);
676
677
  res.end();
677
678
  });
678
679
  return;
679
680
  }
680
681
  } catch (err2) {
681
682
  recordRequest(key, false, err2.message);
682
- recordBandit(complexityBucket, key, false);
683
+ if (!isTransientLimit(err2.statusCode)) recordBandit(complexityBucket, key, false);
683
684
  recordFailure(key, err2.statusCode);
684
685
  }
685
686
  }