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.
- app/core/config/settings.py +8 -8
- app/core/handlers/__init__.py +3 -0
- app/core/handlers/exceptions.py +39 -0
- app/core/middleware/__init__.py +9 -0
- app/core/middleware/api_errors.py +33 -0
- app/core/middleware/request_decompression.py +101 -0
- app/core/middleware/request_id.py +27 -0
- app/core/openai/chat_requests.py +172 -0
- app/core/openai/chat_responses.py +534 -0
- app/core/openai/message_coercion.py +60 -0
- app/core/openai/models_catalog.py +72 -0
- app/core/openai/requests.py +4 -4
- app/core/openai/v1_requests.py +4 -60
- app/db/session.py +25 -8
- app/dependencies.py +43 -16
- app/main.py +12 -67
- app/modules/accounts/repository.py +21 -9
- app/modules/proxy/api.py +58 -0
- app/modules/proxy/load_balancer.py +75 -58
- app/modules/proxy/repo_bundle.py +23 -0
- app/modules/proxy/service.py +98 -102
- app/modules/request_logs/repository.py +3 -0
- app/modules/usage/service.py +65 -4
- {codex_lb-0.4.0.dist-info → codex_lb-0.5.0.dist-info}/METADATA +3 -2
- {codex_lb-0.4.0.dist-info → codex_lb-0.5.0.dist-info}/RECORD +28 -17
- {codex_lb-0.4.0.dist-info → codex_lb-0.5.0.dist-info}/WHEEL +0 -0
- {codex_lb-0.4.0.dist-info → codex_lb-0.5.0.dist-info}/entry_points.txt +0 -0
- {codex_lb-0.4.0.dist-info → codex_lb-0.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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]]
|
app/modules/proxy/service.py
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
|
428
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
app/modules/usage/service.py
CHANGED
|
@@ -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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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.
|
|
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=
|
|
4
|
-
app/main.py,sha256=
|
|
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=
|
|
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=
|
|
27
|
-
app/core/openai/v1_requests.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
|
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=
|
|
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=
|
|
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.
|
|
94
|
-
codex_lb-0.
|
|
95
|
-
codex_lb-0.
|
|
96
|
-
codex_lb-0.
|
|
97
|
-
codex_lb-0.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|