codex-lb 0.4.0__py3-none-any.whl → 0.5.0__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.
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass
5
+ from typing import AsyncContextManager
6
+
7
+ from app.modules.accounts.repository import AccountsRepository
8
+ from app.modules.proxy.sticky_repository import StickySessionsRepository
9
+ from app.modules.request_logs.repository import RequestLogsRepository
10
+ from app.modules.settings.repository import SettingsRepository
11
+ from app.modules.usage.repository import UsageRepository
12
+
13
+
14
+ @dataclass(slots=True)
15
+ class ProxyRepositories:
16
+ accounts: AccountsRepository
17
+ usage: UsageRepository
18
+ request_logs: RequestLogsRepository
19
+ sticky_sessions: StickySessionsRepository
20
+ settings: SettingsRepository
21
+
22
+
23
+ ProxyRepoFactory = Callable[[], AsyncContextManager[ProxyRepositories]]
@@ -29,7 +29,6 @@ from app.core.utils.sse import format_sse_event
29
29
  from app.core.utils.time import utcnow
30
30
  from app.db.models import Account, UsageHistory
31
31
  from app.modules.accounts.auth_manager import AuthManager
32
- from app.modules.accounts.repository import AccountsRepository
33
32
  from app.modules.proxy.helpers import (
34
33
  _apply_error_metadata,
35
34
  _credits_headers,
@@ -46,33 +45,18 @@ from app.modules.proxy.helpers import (
46
45
  _window_snapshot,
47
46
  )
48
47
  from app.modules.proxy.load_balancer import LoadBalancer
49
- from app.modules.proxy.sticky_repository import StickySessionsRepository
48
+ from app.modules.proxy.repo_bundle import ProxyRepoFactory, ProxyRepositories
50
49
  from app.modules.proxy.types import RateLimitStatusPayloadData
51
- from app.modules.request_logs.repository import RequestLogsRepository
52
- from app.modules.settings.repository import SettingsRepository
53
- from app.modules.usage.repository import UsageRepository
54
50
  from app.modules.usage.updater import UsageUpdater
55
51
 
56
52
  logger = logging.getLogger(__name__)
57
53
 
58
54
 
59
55
  class ProxyService:
60
- def __init__(
61
- self,
62
- accounts_repo: AccountsRepository,
63
- usage_repo: UsageRepository,
64
- logs_repo: RequestLogsRepository,
65
- sticky_repo: StickySessionsRepository,
66
- settings_repo: SettingsRepository,
67
- ) -> None:
68
- self._accounts_repo = accounts_repo
69
- self._usage_repo = usage_repo
70
- self._logs_repo = logs_repo
71
- self._settings_repo = settings_repo
56
+ def __init__(self, repo_factory: ProxyRepoFactory) -> None:
57
+ self._repo_factory = repo_factory
72
58
  self._encryptor = TokenEncryptor()
73
- self._auth_manager = AuthManager(accounts_repo)
74
- self._load_balancer = LoadBalancer(accounts_repo, usage_repo, sticky_repo)
75
- self._usage_updater = UsageUpdater(usage_repo, accounts_repo)
59
+ self._load_balancer = LoadBalancer(repo_factory)
76
60
 
77
61
  def stream_responses(
78
62
  self,
@@ -98,9 +82,11 @@ class ProxyService:
98
82
  _maybe_log_proxy_request_payload("compact", payload, headers)
99
83
  _maybe_log_proxy_request_shape("compact", payload, headers)
100
84
  filtered = filter_inbound_headers(headers)
101
- settings = await self._settings_repo.get_or_create()
102
- prefer_earlier_reset = settings.prefer_earlier_reset_accounts
103
- sticky_key = _sticky_key_from_compact_payload(payload) if settings.sticky_threads_enabled else None
85
+ async with self._repo_factory() as repos:
86
+ settings = await repos.settings.get_or_create()
87
+ prefer_earlier_reset = settings.prefer_earlier_reset_accounts
88
+ sticky_threads_enabled = settings.sticky_threads_enabled
89
+ sticky_key = _sticky_key_from_compact_payload(payload) if sticky_threads_enabled else None
104
90
  selection = await self._load_balancer.select_account(
105
91
  sticky_key=sticky_key,
106
92
  reallocate_sticky=sticky_key is not None,
@@ -139,69 +125,71 @@ class ProxyService:
139
125
 
140
126
  async def rate_limit_headers(self) -> dict[str, str]:
141
127
  now = utcnow()
142
- accounts = await self._accounts_repo.list_accounts()
143
- account_map = {account.id: account for account in accounts}
144
-
145
128
  headers: dict[str, str] = {}
146
- primary_minutes = await self._usage_repo.latest_window_minutes("primary")
147
- if primary_minutes is None:
148
- primary_minutes = usage_core.default_window_minutes("primary")
149
- if primary_minutes:
150
- primary_rows = await self._usage_repo.aggregate_since(
151
- now - timedelta(minutes=primary_minutes),
152
- window="primary",
153
- )
154
- if primary_rows:
155
- summary = usage_core.summarize_usage_window(
156
- [row.to_window_row() for row in primary_rows],
157
- account_map,
158
- "primary",
129
+ async with self._repo_factory() as repos:
130
+ accounts = await repos.accounts.list_accounts()
131
+ account_map = {account.id: account for account in accounts}
132
+
133
+ primary_minutes = await repos.usage.latest_window_minutes("primary")
134
+ if primary_minutes is None:
135
+ primary_minutes = usage_core.default_window_minutes("primary")
136
+ if primary_minutes:
137
+ primary_rows = await repos.usage.aggregate_since(
138
+ now - timedelta(minutes=primary_minutes),
139
+ window="primary",
159
140
  )
160
- headers.update(_rate_limit_headers("primary", summary))
161
-
162
- secondary_minutes = await self._usage_repo.latest_window_minutes("secondary")
163
- if secondary_minutes is None:
164
- secondary_minutes = usage_core.default_window_minutes("secondary")
165
- if secondary_minutes:
166
- secondary_rows = await self._usage_repo.aggregate_since(
167
- now - timedelta(minutes=secondary_minutes),
168
- window="secondary",
169
- )
170
- if secondary_rows:
171
- summary = usage_core.summarize_usage_window(
172
- [row.to_window_row() for row in secondary_rows],
173
- account_map,
174
- "secondary",
141
+ if primary_rows:
142
+ summary = usage_core.summarize_usage_window(
143
+ [row.to_window_row() for row in primary_rows],
144
+ account_map,
145
+ "primary",
146
+ )
147
+ headers.update(_rate_limit_headers("primary", summary))
148
+
149
+ secondary_minutes = await repos.usage.latest_window_minutes("secondary")
150
+ if secondary_minutes is None:
151
+ secondary_minutes = usage_core.default_window_minutes("secondary")
152
+ if secondary_minutes:
153
+ secondary_rows = await repos.usage.aggregate_since(
154
+ now - timedelta(minutes=secondary_minutes),
155
+ window="secondary",
175
156
  )
176
- headers.update(_rate_limit_headers("secondary", summary))
157
+ if secondary_rows:
158
+ summary = usage_core.summarize_usage_window(
159
+ [row.to_window_row() for row in secondary_rows],
160
+ account_map,
161
+ "secondary",
162
+ )
163
+ headers.update(_rate_limit_headers("secondary", summary))
177
164
 
178
- latest_usage = await self._usage_repo.latest_by_account()
179
- headers.update(_credits_headers(latest_usage.values()))
165
+ latest_usage = await repos.usage.latest_by_account()
166
+ headers.update(_credits_headers(latest_usage.values()))
180
167
  return headers
181
168
 
182
169
  async def get_rate_limit_payload(self) -> RateLimitStatusPayloadData:
183
- accounts = await self._accounts_repo.list_accounts()
184
- await self._refresh_usage(accounts)
185
- selected_accounts = _select_accounts_for_limits(accounts)
186
- if not selected_accounts:
187
- return RateLimitStatusPayloadData(plan_type="guest")
188
-
189
- account_map = {account.id: account for account in selected_accounts}
190
- primary_rows = await self._latest_usage_rows(account_map, "primary")
191
- secondary_rows = await self._latest_usage_rows(account_map, "secondary")
192
-
193
- primary_summary = _summarize_window(primary_rows, account_map, "primary")
194
- secondary_summary = _summarize_window(secondary_rows, account_map, "secondary")
195
-
196
- now_epoch = int(time.time())
197
- primary_window = _window_snapshot(primary_summary, primary_rows, "primary", now_epoch)
198
- secondary_window = _window_snapshot(secondary_summary, secondary_rows, "secondary", now_epoch)
199
-
200
- return RateLimitStatusPayloadData(
201
- plan_type=_plan_type_for_accounts(selected_accounts),
202
- rate_limit=_rate_limit_details(primary_window, secondary_window),
203
- credits=_credits_snapshot(await self._latest_usage_entries(account_map)),
204
- )
170
+ async with self._repo_factory() as repos:
171
+ accounts = await repos.accounts.list_accounts()
172
+ await self._refresh_usage(repos, accounts)
173
+ selected_accounts = _select_accounts_for_limits(accounts)
174
+ if not selected_accounts:
175
+ return RateLimitStatusPayloadData(plan_type="guest")
176
+
177
+ account_map = {account.id: account for account in selected_accounts}
178
+ primary_rows = await self._latest_usage_rows(repos, account_map, "primary")
179
+ secondary_rows = await self._latest_usage_rows(repos, account_map, "secondary")
180
+
181
+ primary_summary = _summarize_window(primary_rows, account_map, "primary")
182
+ secondary_summary = _summarize_window(secondary_rows, account_map, "secondary")
183
+
184
+ now_epoch = int(time.time())
185
+ primary_window = _window_snapshot(primary_summary, primary_rows, "primary", now_epoch)
186
+ secondary_window = _window_snapshot(secondary_summary, secondary_rows, "secondary", now_epoch)
187
+
188
+ return RateLimitStatusPayloadData(
189
+ plan_type=_plan_type_for_accounts(selected_accounts),
190
+ rate_limit=_rate_limit_details(primary_window, secondary_window),
191
+ credits=_credits_snapshot(await self._latest_usage_entries(repos, account_map)),
192
+ )
205
193
 
206
194
  async def _stream_with_retry(
207
195
  self,
@@ -211,9 +199,11 @@ class ProxyService:
211
199
  propagate_http_errors: bool,
212
200
  ) -> AsyncIterator[str]:
213
201
  request_id = ensure_request_id()
214
- settings = await self._settings_repo.get_or_create()
215
- prefer_earlier_reset = settings.prefer_earlier_reset_accounts
216
- sticky_key = _sticky_key_from_payload(payload) if settings.sticky_threads_enabled else None
202
+ async with self._repo_factory() as repos:
203
+ settings = await repos.settings.get_or_create()
204
+ prefer_earlier_reset = settings.prefer_earlier_reset_accounts
205
+ sticky_threads_enabled = settings.sticky_threads_enabled
206
+ sticky_key = _sticky_key_from_payload(payload) if sticky_threads_enabled else None
217
207
  max_attempts = 3
218
208
  for attempt in range(max_attempts):
219
209
  selection = await self._load_balancer.select_account(
@@ -401,20 +391,21 @@ class ProxyService:
401
391
  )
402
392
  with anyio.CancelScope(shield=True):
403
393
  try:
404
- await self._logs_repo.add_log(
405
- account_id=account_id_value,
406
- request_id=request_id,
407
- model=model,
408
- input_tokens=input_tokens,
409
- output_tokens=output_tokens,
410
- cached_input_tokens=cached_input_tokens,
411
- reasoning_tokens=reasoning_tokens,
412
- reasoning_effort=reasoning_effort,
413
- latency_ms=latency_ms,
414
- status=status,
415
- error_code=error_code,
416
- error_message=error_message,
417
- )
394
+ async with self._repo_factory() as repos:
395
+ await repos.request_logs.add_log(
396
+ account_id=account_id_value,
397
+ request_id=request_id,
398
+ model=model,
399
+ input_tokens=input_tokens,
400
+ output_tokens=output_tokens,
401
+ cached_input_tokens=cached_input_tokens,
402
+ reasoning_tokens=reasoning_tokens,
403
+ reasoning_effort=reasoning_effort,
404
+ latency_ms=latency_ms,
405
+ status=status,
406
+ error_code=error_code,
407
+ error_message=error_message,
408
+ )
418
409
  except Exception:
419
410
  logger.warning(
420
411
  "Failed to persist request log account_id=%s request_id=%s",
@@ -423,18 +414,20 @@ class ProxyService:
423
414
  exc_info=True,
424
415
  )
425
416
 
426
- async def _refresh_usage(self, accounts: list[Account]) -> None:
427
- latest_usage = await self._usage_repo.latest_by_account(window="primary")
428
- await self._usage_updater.refresh_accounts(accounts, latest_usage)
417
+ async def _refresh_usage(self, repos: ProxyRepositories, accounts: list[Account]) -> None:
418
+ latest_usage = await repos.usage.latest_by_account(window="primary")
419
+ updater = UsageUpdater(repos.usage, repos.accounts)
420
+ await updater.refresh_accounts(accounts, latest_usage)
429
421
 
430
422
  async def _latest_usage_rows(
431
423
  self,
424
+ repos: ProxyRepositories,
432
425
  account_map: dict[str, Account],
433
426
  window: str,
434
427
  ) -> list[UsageWindowRow]:
435
428
  if not account_map:
436
429
  return []
437
- latest = await self._usage_repo.latest_by_account(window=window)
430
+ latest = await repos.usage.latest_by_account(window=window)
438
431
  return [
439
432
  UsageWindowRow(
440
433
  account_id=entry.account_id,
@@ -448,15 +441,18 @@ class ProxyService:
448
441
 
449
442
  async def _latest_usage_entries(
450
443
  self,
444
+ repos: ProxyRepositories,
451
445
  account_map: dict[str, Account],
452
446
  ) -> list[UsageHistory]:
453
447
  if not account_map:
454
448
  return []
455
- latest = await self._usage_repo.latest_by_account()
449
+ latest = await repos.usage.latest_by_account()
456
450
  return [entry for entry in latest.values() if entry.account_id in account_map]
457
451
 
458
452
  async def _ensure_fresh(self, account: Account, *, force: bool = False) -> Account:
459
- return await self._auth_manager.ensure_fresh(account, force=force)
453
+ async with self._repo_factory() as repos:
454
+ auth_manager = AuthManager(repos.accounts)
455
+ return await auth_manager.ensure_fresh(account, force=force)
460
456
 
461
457
  async def _handle_proxy_error(self, account: Account, exc: ProxyResponseError) -> None:
462
458
  error = _parse_openai_error(exc.payload)
@@ -4,6 +4,7 @@ from datetime import datetime
4
4
 
5
5
  import anyio
6
6
  from sqlalchemy import String, and_, cast, or_, select
7
+ from sqlalchemy import exc as sa_exc
7
8
  from sqlalchemy.ext.asyncio import AsyncSession
8
9
 
9
10
  from app.core.utils.request_id import ensure_request_id
@@ -56,6 +57,8 @@ class RequestLogsRepository:
56
57
  await self._session.commit()
57
58
  await self._session.refresh(log)
58
59
  return log
60
+ except sa_exc.ResourceClosedError:
61
+ return log
59
62
  except BaseException:
60
63
  await _safe_rollback(self._session)
61
64
  raise
@@ -1,7 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
4
+ import weakref
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
3
7
  from datetime import timedelta
4
- from typing import cast
8
+ from typing import AsyncContextManager, ClassVar, cast
5
9
 
6
10
  from app.core import usage as usage_core
7
11
  from app.core.usage.logs import (
@@ -37,17 +41,30 @@ from app.modules.usage.schemas import (
37
41
  from app.modules.usage.updater import UsageUpdater
38
42
 
39
43
 
44
+ @dataclass(slots=True)
45
+ class _RefreshState:
46
+ lock: asyncio.Lock
47
+ task: asyncio.Task[None] | None = None
48
+
49
+
40
50
  class UsageService:
51
+ _refresh_states: ClassVar[weakref.WeakKeyDictionary[asyncio.AbstractEventLoop, _RefreshState]] = (
52
+ weakref.WeakKeyDictionary()
53
+ )
54
+
41
55
  def __init__(
42
56
  self,
43
57
  usage_repo: UsageRepository,
44
58
  logs_repo: RequestLogsRepository,
45
59
  accounts_repo: AccountsRepository,
60
+ refresh_repo_factory: Callable[[], AsyncContextManager[tuple[UsageRepository, AccountsRepository]]]
61
+ | None = None,
46
62
  ) -> None:
47
63
  self._usage_repo = usage_repo
48
64
  self._logs_repo = logs_repo
49
65
  self._accounts_repo = accounts_repo
50
66
  self._usage_updater = UsageUpdater(usage_repo, accounts_repo)
67
+ self._refresh_repo_factory = refresh_repo_factory
51
68
 
52
69
  async def get_usage_summary(self) -> UsageSummaryResponse:
53
70
  await self._refresh_usage()
@@ -114,9 +131,53 @@ class UsageService:
114
131
  )
115
132
 
116
133
  async def _refresh_usage(self) -> None:
117
- accounts = await self._accounts_repo.list_accounts()
118
- latest_usage = await self._usage_repo.latest_by_account(window="primary")
119
- await self._usage_updater.refresh_accounts(accounts, latest_usage)
134
+ state = self._refresh_state()
135
+ task = state.task
136
+ if task and not task.done():
137
+ await asyncio.shield(task)
138
+ return
139
+
140
+ created = False
141
+ async with state.lock:
142
+ task = state.task
143
+ if not task or task.done():
144
+ task = asyncio.create_task(self._refresh_usage_once())
145
+ state.task = task
146
+ created = True
147
+
148
+ if task is None:
149
+ return
150
+
151
+ try:
152
+ if created:
153
+ await asyncio.shield(task)
154
+ else:
155
+ await asyncio.shield(task)
156
+ finally:
157
+ if task.done() and state.task is task:
158
+ state.task = None
159
+
160
+ async def _refresh_usage_once(self) -> None:
161
+ if self._refresh_repo_factory is None:
162
+ accounts = await self._accounts_repo.list_accounts()
163
+ latest_usage = await self._usage_repo.latest_by_account(window="primary")
164
+ await self._usage_updater.refresh_accounts(accounts, latest_usage)
165
+ return
166
+
167
+ async with self._refresh_repo_factory() as (usage_repo, accounts_repo):
168
+ latest_usage = await usage_repo.latest_by_account(window="primary")
169
+ accounts = await accounts_repo.list_accounts()
170
+ updater = UsageUpdater(usage_repo, accounts_repo)
171
+ await updater.refresh_accounts(accounts, latest_usage)
172
+
173
+ @classmethod
174
+ def _refresh_state(cls) -> _RefreshState:
175
+ loop = asyncio.get_running_loop()
176
+ state = cls._refresh_states.get(loop)
177
+ if state is None:
178
+ state = _RefreshState(lock=asyncio.Lock())
179
+ cls._refresh_states[loop] = state
180
+ return state
120
181
 
121
182
  async def _latest_usage_rows(self, window: str) -> list[UsageWindowRow]:
122
183
  latest = await self._usage_repo.latest_by_account(window=window)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-lb
3
- Version: 0.4.0
3
+ Version: 0.5.0
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>
@@ -49,6 +49,7 @@ Requires-Dist: pydantic>=2.12.5
49
49
  Requires-Dist: python-dotenv>=1.2.1
50
50
  Requires-Dist: python-multipart>=0.0.21
51
51
  Requires-Dist: sqlalchemy>=2.0.45
52
+ Requires-Dist: zstandard>=0.25.0
52
53
  Description-Content-Type: text/markdown
53
54
 
54
55
  <!--
@@ -158,7 +159,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
158
159
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Soju06"><img src="https://avatars.githubusercontent.com/u/34199905?v=4?s=100" width="100px;" alt="Soju06"/><br /><sub><b>Soju06</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=Soju06" title="Code">💻</a> <a href="https://github.com/Soju06/codex-lb/commits?author=Soju06" title="Tests">⚠️</a> <a href="#maintenance-Soju06" title="Maintenance">🚧</a> <a href="#infra-Soju06" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
159
160
  <td align="center" valign="top" width="14.28%"><a href="http://jonas.kamsker.at/"><img src="https://avatars.githubusercontent.com/u/11245306?v=4?s=100" width="100px;" alt="Jonas Kamsker"/><br /><sub><b>Jonas Kamsker</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=JKamsker" title="Code">💻</a> <a href="https://github.com/Soju06/codex-lb/issues?q=author%3AJKamsker" title="Bug reports">🐛</a> <a href="#maintenance-JKamsker" title="Maintenance">🚧</a></td>
160
161
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/Quack6765"><img src="https://avatars.githubusercontent.com/u/5446230?v=4?s=100" width="100px;" alt="Quack"/><br /><sub><b>Quack</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=Quack6765" title="Code">💻</a> <a href="https://github.com/Soju06/codex-lb/issues?q=author%3AQuack6765" title="Bug reports">🐛</a> <a href="#maintenance-Quack6765" title="Maintenance">🚧</a> <a href="#design-Quack6765" title="Design">🎨</a></td>
161
- <td align="center" valign="top" width="14.28%"><a href="https://github.com/hhsw2015"><img src="https://avatars.githubusercontent.com/u/103614420?v=4?s=100" width="100px;" alt="Jill Kok, San Mou"/><br /><sub><b>Jill Kok, San Mou</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=hhsw2015" title="Code">💻</a> <a href="https://github.com/Soju06/codex-lb/commits?author=hhsw2015" title="Tests">⚠️</a></td>
162
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/hhsw2015"><img src="https://avatars.githubusercontent.com/u/103614420?v=4?s=100" width="100px;" alt="Jill Kok, San Mou"/><br /><sub><b>Jill Kok, San Mou</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=hhsw2015" title="Code">💻</a> <a href="https://github.com/Soju06/codex-lb/commits?author=hhsw2015" title="Tests">⚠️</a> <a href="#maintenance-hhsw2015" title="Maintenance">🚧</a></td>
162
163
  <td align="center" valign="top" width="14.28%"><a href="https://github.com/pcy06"><img src="https://avatars.githubusercontent.com/u/44970486?v=4?s=100" width="100px;" alt="PARK CHANYOUNG"/><br /><sub><b>PARK CHANYOUNG</b></sub></a><br /><a href="https://github.com/Soju06/codex-lb/commits?author=pcy06" title="Documentation">📖</a></td>
163
164
  </tr>
164
165
  </tbody>
@@ -1,7 +1,7 @@
1
1
  app/__init__.py,sha256=uqZSnn_VEL8TIUxsYqdf4zA2ByJYfjA06fArVAzrHFo,89
2
2
  app/cli.py,sha256=gkIAkYOT9SbQjUDnVmwhVKZeKjL3YJCMrOjFINwBx54,544
3
- app/dependencies.py,sha256=kfB_TxeZve_cnBxhOHZezKwkOwTNFwoHzNCJDUkGJD8,4377
4
- app/main.py,sha256=7J8mkp_nDqYTo68gu1OwbwsZrVmVG1XVYsmHGXXQm-0,4709
3
+ app/dependencies.py,sha256=30erUmkoFtZUuWJmky7U-hFKetnxwe77mq4Ocua2HeE,5151
4
+ app/main.py,sha256=_BQDwLDcQUOlmXohQfMLlSpXfibZzPul8DMU6rGOoig,2463
5
5
  app/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  app/core/crypto.py,sha256=zUz2GVqXigzXB5zX2Diq2cR41Kl37bBqxJiZmWGiIcY,1145
7
7
  app/core/errors.py,sha256=go4Q5vv6_Rt8ZS230Mp436yCViEKu_xICylGF0gvGJg,1805
@@ -19,12 +19,22 @@ app/core/clients/oauth.py,sha256=XgAzQVAMudBUCQ9nGKUC9N7zagSiawBdhIVmxf9HHwQ,118
19
19
  app/core/clients/proxy.py,sha256=lplog7s1pg1OJb8ZEM1wGbgM_omTUWLkBRZ4McLGevY,10367
20
20
  app/core/clients/usage.py,sha256=kG7TXqmy8IX9m4wJx5fOGDB2hqunivOU6o2xfoXCGy4,4800
21
21
  app/core/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- app/core/config/settings.py,sha256=hI4WwQnEajV36e5UIOJZWiF-ghXxVzfPC8BjMMDOVU0,2692
22
+ app/core/config/settings.py,sha256=dMA4DmW82nOisIZk0me6Bc8NR4OmpS_Y-tCYNFNeXm0,2844
23
+ app/core/handlers/__init__.py,sha256=iyLUtPdBMYjK0mGpyRZ8hII8hiLF5jLR2jHiSI0C2zU,102
24
+ app/core/handlers/exceptions.py,sha256=edNaaPSPweBUSIpBUjV3GWKVTQkARngQV4Oe32p32tI,1453
25
+ app/core/middleware/__init__.py,sha256=3u3ibH0TTkvMjpN7tDu2phDBdTsssSFsyDJPAhq0Um0,372
26
+ app/core/middleware/api_errors.py,sha256=W5FpmbWiQ2rGfNCOHUums_Es7Qz7ktzCNONdj27k2Ps,1046
27
+ app/core/middleware/request_decompression.py,sha256=PwqibE0pwyZW6o2KrpNWcZhyVs07rYEQTY8xEwFoGYo,3667
28
+ app/core/middleware/request_id.py,sha256=f2CMCTy9HbHM_QfWs-EvqIAOHxLhuuC5Sd13ecUKrpc,924
23
29
  app/core/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ app/core/openai/chat_requests.py,sha256=M1Mfu4_WNb5f5PXg4-SMjMWGtI1gkhweIfa1ENANz3k,6787
31
+ app/core/openai/chat_responses.py,sha256=yQTVmX1hbF4M3bZYLsVvzPWv77tqBE_LNPSaRF4tl1A,17455
32
+ app/core/openai/message_coercion.py,sha256=m4_70ygkpBccYhO5VNNTfqFs9kJvDCCEsYmDqpL9P-M,2117
24
33
  app/core/openai/models.py,sha256=SzVPqmp9IDbJTW6gLGHeLxrpAmFehJPSC4fVsqzEmQA,3344
34
+ app/core/openai/models_catalog.py,sha256=vgQubYSV6bDy2iPpyeKsS2ZG5XoVJQEJOYPMwjo-j0o,2840
25
35
  app/core/openai/parsing.py,sha256=VuE1OyPAv1umrSbkzqa6dcjPYfk00isOVB3O0xUPLBw,1537
26
- app/core/openai/requests.py,sha256=p-by1zrNAUVNJMjTf3KHT9g9dZs1MmVrqxVpIT9-X80,2252
27
- app/core/openai/v1_requests.py,sha256=4WIGXwmkXSgoSFtseJIIYiuAx7WT3gqQWYlZhKnOFlc,5612
36
+ app/core/openai/requests.py,sha256=MssrvOzoNb55ImDbP6yf_T0JDjnho0QXS_Eyv-nGI4s,2290
37
+ app/core/openai/v1_requests.py,sha256=gx_C7H8FHgSHNjvuQ00WfiObABHRbvKya_wJl-19kJg,3649
28
38
  app/core/usage/__init__.py,sha256=8SBpiClJR2wl653Cj1DTvmVUvi2jzU6kKXJtq91ofmE,5726
29
39
  app/core/usage/logs.py,sha256=8TrE9nccEjIJslMfAk8AJLmX_Zzeksu8qMTvrGwePx0,2129
30
40
  app/core/usage/models.py,sha256=FtBQx4Rb7jpwcqxmGXxg7RTVV17LK1QOSWaJIkaaNoQ,878
@@ -38,7 +48,7 @@ app/core/utils/sse.py,sha256=DJMOU4vW5Ir_4WeL5t5t7i33aRMnqVPU0eQvGn4sBv8,537
38
48
  app/core/utils/time.py,sha256=B6FfSe43Eq_puE6eourly1X3gajyihK2VOAwJ8M3wyI,497
39
49
  app/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
50
  app/db/models.py,sha256=vjQ2C3l8J1zlsRz9gVzM4fbcX43NGrj51cfCsM4EjNk,5304
41
- app/db/session.py,sha256=T_UdGcqzn5YXo-fuRW8s5xJQvOuJsV4eTSZE0_V2C44,3910
51
+ app/db/session.py,sha256=U51SC4oDIHgoiW0pc5upCjvKyL896j3aYkyfH3EmJ58,4578
42
52
  app/db/migrations/__init__.py,sha256=hRZVLgAQfP-okuhIiKkQtwfnPtBvYXcLJTqg0Yex1Vc,2633
43
53
  app/db/migrations/versions/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
44
54
  app/db/migrations/versions/add_accounts_chatgpt_account_id.py,sha256=5QHfko2f7dq0G34Cqe8CkcKbQor2W5LegfnVu_HgZN8,978
@@ -50,7 +60,7 @@ app/modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
60
  app/modules/accounts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
61
  app/modules/accounts/api.py,sha256=rSkYrg3_p_JE1kHZ4xXa5lBFTdT40v1oNAmlTDGOguY,2807
52
62
  app/modules/accounts/auth_manager.py,sha256=YUKdeHbszRLYZ4uRQzvk6ICk2cB73LjlJMEiWphBWWw,4682
53
- app/modules/accounts/repository.py,sha256=hu7yQsGfPmHOBy7BM3aKlrZuNr4_qPjDDXcAneX1K2c,3730
63
+ app/modules/accounts/repository.py,sha256=ZhDQ5kjesB4MrJjQ_ZQ8n8HexQUMCuw5RJ0w6y6uhfc,4144
54
64
  app/modules/accounts/schemas.py,sha256=gtlbPg5uxM3t_V5JxCL6eP-UaU6TSE0UoX2yIpxM_a0,1659
55
65
  app/modules/accounts/service.py,sha256=M3_SRD21yp0YN820IHdqNIlZsynNsSUN6TO6RcESDeI,8973
56
66
  app/modules/health/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -62,16 +72,17 @@ app/modules/oauth/schemas.py,sha256=sdDKP7u9bO87lcZXjK7uSokurHPS22nN2p_jkw9iEBc,
62
72
  app/modules/oauth/service.py,sha256=fEzUtoq1g-20NgWZlB_maA2XB-scMa0HJV24SxZPnqQ,12908
63
73
  app/modules/oauth/templates/oauth_success.html,sha256=YNSGUIozcZEJQjpFtM2sgF4n8jqfbmx8LRwdXTraym4,3799
64
74
  app/modules/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
- app/modules/proxy/api.py,sha256=HhJPBa-I6EL0N4jqkDtP1ZrTHNq5yt2Kzn0D0LJK7QQ,4032
75
+ app/modules/proxy/api.py,sha256=quBEQzF32tnsbk72tEVqx8NrqwlTYkqZthH4oe-ndBs,6174
66
76
  app/modules/proxy/helpers.py,sha256=-XVTNTpkDvJ3KfsuGHztfUVBGBFm2H-AaNTNH2dNe6o,8739
67
- app/modules/proxy/load_balancer.py,sha256=Forib096qbWud11aKw9TRbBN1bIQjOmE3ZYY62vhVYk,9831
77
+ app/modules/proxy/load_balancer.py,sha256=6Tbeba1zaYEfCctCC5VMUgDF0jXK2ICzeiFqEhYYQSU,10618
78
+ app/modules/proxy/repo_bundle.py,sha256=yP4ggYpHNvKTfHD-YOqQ0oL2PlR7qFwmmasWMO155LI,776
68
79
  app/modules/proxy/schemas.py,sha256=55pXtUCl2R_93kAPOJJ7Ji4Jn3qVu10vq2KSCCkNdp4,2748
69
- app/modules/proxy/service.py,sha256=4lnOoqvCNiRhCVOcshnKslbiH4O35iYn1S3CykJkhMA,24578
80
+ app/modules/proxy/service.py,sha256=-hlz26DMuEqv6kH8g_5kfnS8yTI-lT-jHq0oWnPydiY,24741
70
81
  app/modules/proxy/sticky_repository.py,sha256=peFaAnaCVr064W-Sh0Kjvz-MuPbBfrh86RLQOGJ6qqs,2177
71
82
  app/modules/proxy/types.py,sha256=iqEyoO8vGr8N5oEzUSvVWCai7UZbJAU62IvO7JNS9qs,927
72
83
  app/modules/request_logs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
84
  app/modules/request_logs/api.py,sha256=NkLHo8Yuy4S6m00yUIOmzcag9scEaSG2QL7CDRYfyUE,3158
74
- app/modules/request_logs/repository.py,sha256=e0-z6-ZfJpMMEShqpK-k23C5y1LnTxawpfMaaiRco40,8560
85
+ app/modules/request_logs/repository.py,sha256=ufX1NhOh8geZ8iFgMRhJY0SG-3r4v8sJHlybdms0tYo,8663
75
86
  app/modules/request_logs/schemas.py,sha256=MFS_MW0mW3cGyIulxJN81dJ3SS2bt0tFE67-DzYX1Pg,944
76
87
  app/modules/request_logs/service.py,sha256=ruZz5QAKYSrJbYpImZzU2va1sLFfamWL8dOhqU-GG0A,5485
77
88
  app/modules/settings/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
@@ -85,13 +96,13 @@ app/modules/usage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
85
96
  app/modules/usage/api.py,sha256=jpPc2VSjmiP6DjRZvotHCh_trLINOngSyTdS7MY9fgg,1108
86
97
  app/modules/usage/repository.py,sha256=DtJI4kgajW7YUJ0JKJjdNCPBXT_fdBwqDoepi9aznyA,4791
87
98
  app/modules/usage/schemas.py,sha256=eCgunQOvQeYEqv9IecjunONPVLpg2MPn_YGzsnBTcpQ,1633
88
- app/modules/usage/service.py,sha256=8-XX8m4TgQteum-a53l0DS-EL2NnC4r9artehfFltvM,10315
99
+ app/modules/usage/service.py,sha256=8nEP6fcd67ZYuaVXIlcGloA2DKzzUMsxVSRZSNkJOeo,12381
89
100
  app/modules/usage/updater.py,sha256=TUFLIjIe8c0m06bnO6iumzAyFuMED3B1ry3Ty5o8JRo,7821
90
101
  app/static/index.css,sha256=4EcLWTHAkhbqldi9fE1Y_bQCtmJuXUgEs0JHV-KmV9w,31757
91
102
  app/static/index.html,sha256=g1U2AlbvGChev5KIyPoVI5TJ3dnvEOqoAot1tENvQOs,39799
92
103
  app/static/index.js,sha256=q-IXWQURYksd-i68yXwyqBazs-AYiz3Tvk5t_B6NbSY,69750
93
- codex_lb-0.4.0.dist-info/METADATA,sha256=VQXBteRPg3SxSUeq9oAQW3x3VRsTsk2KWHxNv-IfKZE,7244
94
- codex_lb-0.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
95
- codex_lb-0.4.0.dist-info/entry_points.txt,sha256=SEa5T6Uz2Fhy574No6Y0XyGmYi3PXLrhu2xStJTqyI8,42
96
- codex_lb-0.4.0.dist-info/licenses/LICENSE,sha256=cHPibxiL0TXwrUX_kNY6ym544EX1UCzKhxdaca5cFuk,1062
97
- codex_lb-0.4.0.dist-info/RECORD,,
104
+ codex_lb-0.5.0.dist-info/METADATA,sha256=ym9TYcpUCgJ66jUQX9uorIxzYr-BFSHwOhXfAW6Vyzc,7338
105
+ codex_lb-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
106
+ codex_lb-0.5.0.dist-info/entry_points.txt,sha256=SEa5T6Uz2Fhy574No6Y0XyGmYi3PXLrhu2xStJTqyI8,42
107
+ codex_lb-0.5.0.dist-info/licenses/LICENSE,sha256=cHPibxiL0TXwrUX_kNY6ym544EX1UCzKhxdaca5cFuk,1062
108
+ codex_lb-0.5.0.dist-info/RECORD,,