codex-lb 0.1.4__py3-none-any.whl → 0.1.5__py3-none-any.whl

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.
@@ -5,7 +5,7 @@ from dataclasses import dataclass
5
5
  from typing import Iterable
6
6
 
7
7
  from app.core.balancer.types import UpstreamError
8
- from app.core.utils.retry import parse_retry_after
8
+ from app.core.utils.retry import backoff_seconds, parse_retry_after
9
9
  from app.db.models import AccountStatus
10
10
 
11
11
  PERMANENT_FAILURE_CODES = {
@@ -22,7 +22,7 @@ class AccountState:
22
22
  account_id: str
23
23
  status: AccountStatus
24
24
  used_percent: float | None = None
25
- reset_at: int | None = None
25
+ reset_at: float | None = None
26
26
  last_error_at: float | None = None
27
27
  last_selected_at: float | None = None
28
28
  error_count: int = 0
@@ -97,18 +97,11 @@ def handle_rate_limit(state: AccountState, error: UpstreamError) -> None:
97
97
  state.status = AccountStatus.RATE_LIMITED
98
98
  state.error_count += 1
99
99
  state.last_error_at = time.time()
100
-
101
- reset_at = _extract_reset_at(error)
102
- if reset_at is not None:
103
- state.reset_at = reset_at
104
- return
105
-
106
100
  message = error.get("message")
107
101
  delay = parse_retry_after(message) if message else None
108
- if delay:
109
- state.reset_at = int(time.time() + delay)
110
- else:
111
- state.reset_at = int(time.time() + 300)
102
+ if delay is None:
103
+ delay = backoff_seconds(state.error_count)
104
+ state.reset_at = time.time() + delay
112
105
 
113
106
 
114
107
  def handle_quota_exceeded(state: AccountState, error: UpstreamError) -> None:
app/core/utils/retry.py CHANGED
@@ -1,8 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import random
3
4
  import re
4
5
 
5
6
  _RETRY_PATTERN = re.compile(r"(?i)try again in\s*(\d+(?:\.\d+)?)\s*(s|ms|seconds?)")
7
+ _BACKOFF_INITIAL_DELAY_MS = 200
8
+ _BACKOFF_FACTOR = 2.0
9
+ _BACKOFF_JITTER_MIN = 0.9
10
+ _BACKOFF_JITTER_MAX = 1.1
6
11
 
7
12
 
8
13
  def parse_retry_after(message: str) -> float | None:
@@ -14,3 +19,12 @@ def parse_retry_after(message: str) -> float | None:
14
19
  if unit == "ms":
15
20
  return value / 1000
16
21
  return value
22
+
23
+
24
+ def backoff_seconds(attempt: int) -> float:
25
+ if attempt < 1:
26
+ attempt = 1
27
+ exponent = _BACKOFF_FACTOR ** (attempt - 1)
28
+ base_ms = _BACKOFF_INITIAL_DELAY_MS * exponent
29
+ jitter = random.uniform(_BACKOFF_JITTER_MIN, _BACKOFF_JITTER_MAX)
30
+ return (base_ms * jitter) / 1000.0
@@ -20,7 +20,7 @@ from app.modules.usage.repository import UsageRepository
20
20
 
21
21
  @dataclass
22
22
  class RuntimeState:
23
- reset_at: int | None = None
23
+ reset_at: float | None = None
24
24
  last_error_at: float | None = None
25
25
  last_selected_at: float | None = None
26
26
  error_count: int = 0
@@ -179,10 +179,10 @@ def _apply_secondary_quota(
179
179
  *,
180
180
  status: AccountStatus,
181
181
  primary_used: float | None,
182
- runtime_reset: int | None,
182
+ runtime_reset: float | None,
183
183
  secondary_used: float | None,
184
184
  secondary_reset: int | None,
185
- ) -> tuple[AccountStatus, float | None, int | None]:
185
+ ) -> tuple[AccountStatus, float | None, float | None]:
186
186
  used_percent = primary_used
187
187
  reset_at = runtime_reset
188
188
 
app/static/index.js CHANGED
@@ -569,16 +569,9 @@
569
569
  return acc;
570
570
  }, {});
571
571
 
572
- const mergeUsageIntoAccounts = (
573
- accounts,
574
- primaryUsage,
575
- secondaryUsage,
576
- summary,
577
- ) => {
572
+ const mergeUsageIntoAccounts = (accounts, primaryUsage, secondaryUsage) => {
578
573
  const primaryMap = buildUsageIndex(primaryUsage || []);
579
574
  const secondaryMap = buildUsageIndex(secondaryUsage || []);
580
- const resetAtPrimary = summary?.primaryWindow?.resetAt ?? null;
581
- const resetAtSecondary = summary?.secondaryWindow?.resetAt ?? null;
582
575
  return accounts.map((account) => {
583
576
  const primaryRow = primaryMap[account.id];
584
577
  const secondaryRow = secondaryMap[account.id];
@@ -598,8 +591,8 @@
598
591
  account.usage?.secondaryRemainingPercent ??
599
592
  0,
600
593
  },
601
- resetAtPrimary: resetAtPrimary ?? account.resetAtPrimary ?? null,
602
- resetAtSecondary: resetAtSecondary ?? account.resetAtSecondary ?? null,
594
+ resetAtPrimary: account.resetAtPrimary ?? null,
595
+ resetAtSecondary: account.resetAtSecondary ?? null,
603
596
  };
604
597
  });
605
598
  };
@@ -1191,7 +1184,6 @@
1191
1184
  accountsResult.value,
1192
1185
  primaryUsage,
1193
1186
  secondaryUsage,
1194
- summary,
1195
1187
  );
1196
1188
  this.applyData(
1197
1189
  {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-lb
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: Codex load balancer and proxy for ChatGPT accounts with usage dashboard
5
5
  Author-email: Soju06 <qlskssk@gmail.com>
6
6
  Maintainer-email: Soju06 <qlskssk@gmail.com>
@@ -10,7 +10,7 @@ app/core/auth/__init__.py,sha256=wR52dbGd66W1haAOUXn5RMzn3bj-EeXm4vm7zqI3kHE,281
10
10
  app/core/auth/models.py,sha256=iyygu0uHK7fu8txyIyCkXmlgqYPbqEZu3IFysD1t-gk,1557
11
11
  app/core/auth/refresh.py,sha256=jhYxT2mQFX4CCvuBQfYNcI53iOtWa0vSkTuZAfbpd9k,5105
12
12
  app/core/balancer/__init__.py,sha256=bGy7gITRPObt6P9NdG3pg8t3qWoSW0XWoADqfNYrhdg,405
13
- app/core/balancer/logic.py,sha256=JFQjzYFzTFRl2Ps24Me-KFZ8jVLMe3Jj5Xzm3aJzFs4,5168
13
+ app/core/balancer/logic.py,sha256=aBuluReYRaYRmXNBntyUhOy1oPsK6Ol4Yt6WSkBLzn4,5059
14
14
  app/core/balancer/types.py,sha256=gDgjlTy-NH3YhHYl2-YYpIabnckN9Q8-4cRy6S1u0K4,191
15
15
  app/core/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  app/core/clients/http.py,sha256=yfFwsaIZNbSmNtyV02WnMKn00JdoNNSlSIx5xB3QY4o,987
@@ -30,7 +30,7 @@ app/core/usage/pricing.py,sha256=6p8rJ26Gk61mz2t_h9sa0T7NiPDUTiNpzoDewMzT6E0,546
30
30
  app/core/usage/types.py,sha256=CbFF6JYSLvALa2P0qYuj9J9b_w53Fgjg0JOu4RzMWbI,2104
31
31
  app/core/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
32
  app/core/utils/request_id.py,sha256=gxafI-Se8dRQTir-HNGelMzC9S2gwYZntTkVzEqzp_I,705
33
- app/core/utils/retry.py,sha256=E1s397Bse072sdeEZ2uri5sPAOlTIdyccB-F9jh_E64,408
33
+ app/core/utils/retry.py,sha256=UmBap1Wh-CBT7r4fHzVb_PI9-LR9-HjUtDzRnhRjP2U,822
34
34
  app/core/utils/sse.py,sha256=ER3_7mm50BUC1CPIKoVOmQikz-vlEWKNSRDEVqzI_KA,387
35
35
  app/core/utils/time.py,sha256=B6FfSe43Eq_puE6eourly1X3gajyihK2VOAwJ8M3wyI,497
36
36
  app/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -52,7 +52,7 @@ app/modules/oauth/templates/oauth_success.html,sha256=YNSGUIozcZEJQjpFtM2sgF4n8j
52
52
  app/modules/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
53
  app/modules/proxy/api.py,sha256=BR_qNlNZg2Ft5GYQ-7AzlLlJFf6RVJmlzJzFr_KHUIc,2921
54
54
  app/modules/proxy/auth_manager.py,sha256=sFcbStd-OK0g8S56z7XcSkFJ22kEirBU0fYdz2drS0o,2185
55
- app/modules/proxy/load_balancer.py,sha256=k53RXn9v9fQPXEJ3zWbkhh_Jd82PK1tUef3ZvCdSzog,7441
55
+ app/modules/proxy/load_balancer.py,sha256=L1a7duA4mcR61huXKU052QNz4JAGgUwhMuOPpv5BV4w,7447
56
56
  app/modules/proxy/schemas.py,sha256=55pXtUCl2R_93kAPOJJ7Ji4Jn3qVu10vq2KSCCkNdp4,2748
57
57
  app/modules/proxy/service.py,sha256=XBpL1OIZuzss8nvsYajC5KsaRdGztjci5RgnWORZ1ss,26816
58
58
  app/modules/proxy/types.py,sha256=iqEyoO8vGr8N5oEzUSvVWCai7UZbJAU62IvO7JNS9qs,927
@@ -72,9 +72,9 @@ app/modules/usage/service.py,sha256=_LQA3-L1FxIpzPoIQ-rxz3n3KjiHpTSxRimZapzwx3o,
72
72
  app/static/7.css,sha256=9EHW2Ouff2fRXgcQbYuCuglxTFgQZGNLLTXKTS6S5aI,80687
73
73
  app/static/index.css,sha256=ct1FfBg0PY7c6KxSS6A7JM_WDwdeJqhQa4pXTgyJ19Q,8786
74
74
  app/static/index.html,sha256=FmWQ0l40rTBsHIR9i0ttGTzG17ePASsO2VDv6Q48CNw,24477
75
- app/static/index.js,sha256=L6Gie0iSAHyrPLvO_QtzEis4rxhVqNe2m-g4h3lB6GU,52419
76
- codex_lb-0.1.4.dist-info/METADATA,sha256=lxv3ILVmcYOqGy3z-7y6_V4izHDFGH_U_m6dwmfHAmo,3828
77
- codex_lb-0.1.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
78
- codex_lb-0.1.4.dist-info/entry_points.txt,sha256=SEa5T6Uz2Fhy574No6Y0XyGmYi3PXLrhu2xStJTqyI8,42
79
- codex_lb-0.1.4.dist-info/licenses/LICENSE,sha256=cHPibxiL0TXwrUX_kNY6ym544EX1UCzKhxdaca5cFuk,1062
80
- codex_lb-0.1.4.dist-info/RECORD,,
75
+ app/static/index.js,sha256=2gbVWp_Ot00NLdPpLE2Cc-YWk38H04x8TZw3OFO2SKk,52209
76
+ codex_lb-0.1.5.dist-info/METADATA,sha256=Cjw3NQBgM5hFgPAQUZ81PFBOI2mAq8SiwViALHOkxxA,3828
77
+ codex_lb-0.1.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
78
+ codex_lb-0.1.5.dist-info/entry_points.txt,sha256=SEa5T6Uz2Fhy574No6Y0XyGmYi3PXLrhu2xStJTqyI8,42
79
+ codex_lb-0.1.5.dist-info/licenses/LICENSE,sha256=cHPibxiL0TXwrUX_kNY6ym544EX1UCzKhxdaca5cFuk,1062
80
+ codex_lb-0.1.5.dist-info/RECORD,,