codex-lb 0.2.0__tar.gz → 0.3.1__tar.gz
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.
- codex_lb-0.3.1/.all-contributorsrc +52 -0
- codex_lb-0.3.1/.github/release-please-manifest.json +3 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/.gitignore +2 -1
- codex_lb-0.3.1/CHANGELOG.md +110 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/PKG-INFO +33 -7
- codex_lb-0.3.1/README.md +81 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/auth/__init__.py +10 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/balancer/logic.py +33 -6
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/config/settings.py +2 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/usage/__init__.py +2 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/usage/logs.py +12 -2
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/usage/quota.py +10 -4
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/usage/types.py +3 -2
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/db/migrations/__init__.py +14 -3
- codex_lb-0.3.1/app/db/migrations/versions/add_accounts_chatgpt_account_id.py +29 -0
- codex_lb-0.3.1/app/db/migrations/versions/add_accounts_reset_at.py +29 -0
- codex_lb-0.3.1/app/db/migrations/versions/add_dashboard_settings.py +31 -0
- codex_lb-0.3.1/app/db/migrations/versions/add_request_logs_reasoning_effort.py +21 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/db/models.py +33 -0
- codex_lb-0.3.1/app/db/session.py +136 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/dependencies.py +27 -1
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/main.py +11 -2
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/accounts/auth_manager.py +44 -3
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/accounts/repository.py +14 -6
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/accounts/service.py +4 -2
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/oauth/service.py +4 -3
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/proxy/load_balancer.py +74 -5
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/proxy/service.py +155 -31
- codex_lb-0.3.1/app/modules/proxy/sticky_repository.py +56 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/request_logs/repository.py +6 -3
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/request_logs/schemas.py +2 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/request_logs/service.py +8 -1
- codex_lb-0.3.1/app/modules/settings/__init__.py +1 -0
- codex_lb-0.3.1/app/modules/settings/api.py +37 -0
- codex_lb-0.3.1/app/modules/settings/repository.py +40 -0
- codex_lb-0.3.1/app/modules/settings/schemas.py +13 -0
- codex_lb-0.3.1/app/modules/settings/service.py +33 -0
- codex_lb-0.3.1/app/modules/shared/schemas.py +22 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/usage/schemas.py +1 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/usage/service.py +17 -1
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/usage/updater.py +36 -7
- codex_lb-0.3.1/app/static/index.css +1248 -0
- codex_lb-0.3.1/app/static/index.html +541 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/static/index.js +327 -49
- {codex_lb-0.2.0 → codex_lb-0.3.1}/docker-compose.yml +2 -0
- codex_lb-0.3.1/docs/screenshots/accounts.jpg +0 -0
- codex_lb-0.3.1/docs/screenshots/dashboard.jpg +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/pyproject.toml +4 -4
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/conftest.py +3 -2
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_accounts_api.py +18 -10
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_accounts_api_extended.py +7 -4
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_db_models.py +1 -1
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_migrations.py +2 -2
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_oauth_flow.py +8 -3
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_proxy_api_extended.py +19 -15
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_proxy_compact.py +14 -6
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_proxy_responses.py +9 -3
- codex_lb-0.3.1/tests/integration/test_proxy_sticky_sessions.py +298 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_repositories.py +24 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_request_logs_api.py +1 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_request_logs_filters.py +3 -0
- codex_lb-0.3.1/tests/integration/test_settings_api.py +32 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_usage_api.py +27 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/unit/test_auth.py +3 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/unit/test_auth_manager.py +2 -0
- codex_lb-0.3.1/tests/unit/test_load_balancer.py +305 -0
- codex_lb-0.3.1/tests/unit/test_usage_updater.py +124 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/uv.lock +138 -122
- codex_lb-0.2.0/.github/release-please-manifest.json +0 -3
- codex_lb-0.2.0/CHANGELOG.md +0 -72
- codex_lb-0.2.0/README.md +0 -55
- codex_lb-0.2.0/app/db/session.py +0 -76
- codex_lb-0.2.0/app/modules/shared/schemas.py +0 -8
- codex_lb-0.2.0/app/static/7.css +0 -1336
- codex_lb-0.2.0/app/static/index.css +0 -543
- codex_lb-0.2.0/app/static/index.html +0 -457
- codex_lb-0.2.0/docs/screenshots/accounts.jpeg +0 -0
- codex_lb-0.2.0/docs/screenshots/dashboard.jpeg +0 -0
- codex_lb-0.2.0/tests/unit/test_load_balancer.py +0 -129
- {codex_lb-0.2.0 → codex_lb-0.3.1}/.dockerignore +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/.env.example +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/.github/release-please-config.json +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/.github/workflows/ci.yml +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/.github/workflows/release-please.yml +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/.github/workflows/release.yml +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/.pre-commit-config.yaml +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/AGENTS.md +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/Dockerfile +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/LICENSE +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/cli.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/auth/models.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/auth/refresh.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/balancer/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/balancer/types.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/clients/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/clients/http.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/clients/oauth.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/clients/proxy.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/clients/usage.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/config/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/crypto.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/errors.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/openai/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/openai/models.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/openai/parsing.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/openai/requests.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/plan_types.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/types.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/usage/models.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/usage/pricing.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/utils/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/utils/request_id.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/utils/retry.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/utils/sse.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/core/utils/time.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/db/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/db/migrations/versions/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/db/migrations/versions/normalize_account_plan_types.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/accounts/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/accounts/api.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/accounts/schemas.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/health/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/health/api.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/health/schemas.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/oauth/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/oauth/api.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/oauth/schemas.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/oauth/templates/oauth_success.html +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/proxy/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/proxy/api.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/proxy/helpers.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/proxy/schemas.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/proxy/types.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/request_logs/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/request_logs/api.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/shared/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/usage/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/usage/api.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/app/modules/usage/repository.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/__init__.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_codex_usage_api.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_health_and_errors.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_load_balancer_integration.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/integration/test_usage_summary.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/unit/test_auth_refresh.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/unit/test_oauth_client.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/unit/test_pricing.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/unit/test_proxy_utils.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/unit/test_retry.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/unit/test_sse.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/unit/test_usage.py +0 -0
- {codex_lb-0.2.0 → codex_lb-0.3.1}/tests/unit/test_usage_client.py +0 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"projectName": "codex-lb",
|
|
3
|
+
"projectOwner": "Soju06",
|
|
4
|
+
"repoType": "github",
|
|
5
|
+
"repoHost": "https://github.com",
|
|
6
|
+
"files": [
|
|
7
|
+
"README.md"
|
|
8
|
+
],
|
|
9
|
+
"imageSize": 100,
|
|
10
|
+
"commit": true,
|
|
11
|
+
"commitConvention": "angular",
|
|
12
|
+
"contributors": [
|
|
13
|
+
{
|
|
14
|
+
"login": "Soju06",
|
|
15
|
+
"name": "Soju06",
|
|
16
|
+
"avatar_url": "https://avatars.githubusercontent.com/u/34199905?v=4",
|
|
17
|
+
"profile": "https://github.com/Soju06",
|
|
18
|
+
"contributions": [
|
|
19
|
+
"code",
|
|
20
|
+
"test",
|
|
21
|
+
"maintenance",
|
|
22
|
+
"infra"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"login": "JKamsker",
|
|
27
|
+
"name": "Jonas Kamsker",
|
|
28
|
+
"avatar_url": "https://avatars.githubusercontent.com/u/11245306?v=4",
|
|
29
|
+
"profile": "http://jonas.kamsker.at/",
|
|
30
|
+
"contributions": [
|
|
31
|
+
"code",
|
|
32
|
+
"bug",
|
|
33
|
+
"maintenance"
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"login": "Quack6765",
|
|
38
|
+
"name": "Quack",
|
|
39
|
+
"avatar_url": "https://avatars.githubusercontent.com/u/5446230?v=4",
|
|
40
|
+
"profile": "https://github.com/Quack6765",
|
|
41
|
+
"contributions": [
|
|
42
|
+
"code",
|
|
43
|
+
"bug",
|
|
44
|
+
"maintenance",
|
|
45
|
+
"design"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"contributorsPerLine": 7,
|
|
50
|
+
"linkToUsage": false,
|
|
51
|
+
"commitType": "docs"
|
|
52
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.3.1](https://github.com/Soju06/codex-lb/compare/v0.3.0...v0.3.1) (2026-01-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Documentation
|
|
7
|
+
|
|
8
|
+
* add Quack6765 as a contributor for design ([7a5ec08](https://github.com/Soju06/codex-lb/commit/7a5ec084b9a8d32c844127739f826a5f83bf1440))
|
|
9
|
+
* update .all-contributorsrc ([14ea9da](https://github.com/Soju06/codex-lb/commit/14ea9da361a978a56c4d1f7facefe789193c7b91))
|
|
10
|
+
* update README.md ([f283d60](https://github.com/Soju06/codex-lb/commit/f283d60ae359585cd128a965ca6fba2a14249a11))
|
|
11
|
+
|
|
12
|
+
## [0.3.0](https://github.com/Soju06/codex-lb/compare/v0.2.0...v0.3.0) (2026-01-21)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* add cached input tokens handling and update related metrics in … ([5bf6609](https://github.com/Soju06/codex-lb/commit/5bf66095b8000ffc8fbdf8d989f60171604f69d3))
|
|
18
|
+
* add cached input tokens handling and update related metrics in logs and usage schemas ([c965036](https://github.com/Soju06/codex-lb/commit/c9650367c1a2d14e63e3440788b7cd44b08ebd9a))
|
|
19
|
+
* add formatting for cached input tokens metadata in metrics display ([53feaa6](https://github.com/Soju06/codex-lb/commit/53feaa62f7c5c282508f37c3fd42d9af655c2fa9))
|
|
20
|
+
* add secondary usage tracking and selection logic for accounts in load balancer ([d66cf69](https://github.com/Soju06/codex-lb/commit/d66cf69b2834b42fefbbfa646d82477f9832fdda))
|
|
21
|
+
* add ty type checking and refactors ([41fa811](https://github.com/Soju06/codex-lb/commit/41fa8112ba9b900ffa5dbee3a39d94267e2caa75))
|
|
22
|
+
* **app:** add migrations and reasoning effort support ([9eae590](https://github.com/Soju06/codex-lb/commit/9eae5903a08363291e397f983a531ddf325658d7))
|
|
23
|
+
* implement dashboard settings for sticky threads and reset preferences ([cd04812](https://github.com/Soju06/codex-lb/commit/cd0481247f0ceffdd92173ea84773960e52a7253))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Bug Fixes
|
|
27
|
+
|
|
28
|
+
* **app:** tune sqlite pragmas and usage UI ([a44a4fd](https://github.com/Soju06/codex-lb/commit/a44a4fd6fe5771282a12ee62a34c9be819254322))
|
|
29
|
+
* **app:** update effort display format in history ([0796740](https://github.com/Soju06/codex-lb/commit/0796740ab570cf476b2285a615559a9a6318082f))
|
|
30
|
+
* **app:** update effort display format to include parentheses ([6fbae96](https://github.com/Soju06/codex-lb/commit/6fbae960f393ff92cae0feb614ca0e811a855851))
|
|
31
|
+
* **dashboard:** fallback primary remaining to summary ([02b3d39](https://github.com/Soju06/codex-lb/commit/02b3d39c2b734271af7c420fc52b7e87350177e1))
|
|
32
|
+
* **db:** avoid leaked async connection in migration ([9aa1d03](https://github.com/Soju06/codex-lb/commit/9aa1d0395481a96a21db2d0add18ee1753f183b2))
|
|
33
|
+
* **db:** use returning for dml checks ([4ec7c7a](https://github.com/Soju06/codex-lb/commit/4ec7c7a6615e6e5852b0865e09184544f09ebedc))
|
|
34
|
+
* **ui:** style and label settings checkboxes ([722cad8](https://github.com/Soju06/codex-lb/commit/722cad851706e2784815dad4069902cc95b3f662))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Documentation
|
|
38
|
+
|
|
39
|
+
* expand 0.2.0 changelog ([32148dc](https://github.com/Soju06/codex-lb/commit/32148dc2d195cec0dd85f61fc0a13d8cbef24e24))
|
|
40
|
+
|
|
41
|
+
## [0.2.0](https://github.com/Soju06/codex-lb/compare/v0.1.5...v0.2.0) (2026-01-19)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### Features
|
|
45
|
+
|
|
46
|
+
* add ty type checking and pre-commit hook
|
|
47
|
+
* add health response schema and typed context cleanup
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
### Bug Fixes
|
|
51
|
+
|
|
52
|
+
* normalize stored plan types (pro/team/business/enterprise/edu) so accounts no longer show as unknown
|
|
53
|
+
* prevent rate-limit status when usage is below 100% by using cooldown/backoff and primary-window quota checks
|
|
54
|
+
* surface per-account quota reset times by applying primary/secondary reset windows with fallbacks
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
### Refactor
|
|
58
|
+
|
|
59
|
+
* move auth/usage helpers into module boundaries and extract proxy helpers
|
|
60
|
+
* tighten typing across services and tests
|
|
61
|
+
|
|
62
|
+
## [0.1.5](https://github.com/Soju06/codex-lb/compare/v0.1.4...v0.1.5) (2026-01-14)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
### Bug Fixes
|
|
66
|
+
|
|
67
|
+
* align rate-limit backoff and reset handling ([4d59650](https://github.com/Soju06/codex-lb/commit/4d596508e5ad13e68aa6e64f9cb32324bd38f07b))
|
|
68
|
+
|
|
69
|
+
## [0.1.4](https://github.com/Soju06/codex-lb/compare/v0.1.3...v0.1.4) (2026-01-13)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
### Bug Fixes
|
|
73
|
+
|
|
74
|
+
* **db:** harden session cleanup on cancellation ([dee3916](https://github.com/Soju06/codex-lb/commit/dee3916efa83dedec1d5ad43e1e14950b8c6e4a7))
|
|
75
|
+
|
|
76
|
+
## [0.1.3](https://github.com/Soju06/codex-lb/compare/v0.1.2...v0.1.3) (2026-01-12)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
### Documentation
|
|
80
|
+
|
|
81
|
+
* use absolute image URLs for PyPI ([5fa65a5](https://github.com/Soju06/codex-lb/commit/5fa65a572980f356738f49be3adf2c62fdc38466))
|
|
82
|
+
|
|
83
|
+
## [0.1.2](https://github.com/Soju06/codex-lb/compare/v0.1.1...v0.1.2) (2026-01-12)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
### Bug Fixes
|
|
87
|
+
|
|
88
|
+
* sync package __version__ ([3dd97e6](https://github.com/Soju06/codex-lb/commit/3dd97e6397a8ea9d3528c166d1e729936f98f737))
|
|
89
|
+
|
|
90
|
+
## [0.1.1](https://github.com/Soju06/codex-lb/compare/v0.1.0...v0.1.1) (2026-01-12)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
### Bug Fixes
|
|
94
|
+
|
|
95
|
+
* address lint warnings ([7c3cc06](https://github.com/Soju06/codex-lb/commit/7c3cc06c9a6a9a9a8895c1dd5fcc57b3c0eebdb3))
|
|
96
|
+
* reactivate accounts when secondary quota clears ([58a4263](https://github.com/Soju06/codex-lb/commit/58a42630d644559f96f045a96c25d0126810542e))
|
|
97
|
+
* skip project install in docker build ([64e9156](https://github.com/Soju06/codex-lb/commit/64e9156075c256ef48c0587ea1abb7cc092b97a5))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
### Documentation
|
|
101
|
+
|
|
102
|
+
* add dashboard hero and accounts view ([3522654](https://github.com/Soju06/codex-lb/commit/3522654fe5d09adbe32895d4b24e8b00faac9dfe))
|
|
103
|
+
|
|
104
|
+
## [0.1.0](https://github.com/Soju06/codex-lb/releases/tag/v0.1.0) (2026-01-07)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
### Bug Fixes
|
|
108
|
+
|
|
109
|
+
* address lint warnings ([7c3cc06](https://github.com/Soju06/codex-lb/commit/7c3cc06c9a6a9a8895c1dd5fcc57b3c0eebdb3))
|
|
110
|
+
* skip project install in docker build ([64e9156](https://github.com/Soju06/codex-lb/commit/64e9156075c256ef48c0587ea1abb7cc092b97a5))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codex-lb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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>
|
|
@@ -39,7 +39,7 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
39
39
|
Classifier: Topic :: System :: Networking
|
|
40
40
|
Requires-Python: >=3.13
|
|
41
41
|
Requires-Dist: aiohttp-retry>=2.9.1
|
|
42
|
-
Requires-Dist: aiohttp>=3.13.
|
|
42
|
+
Requires-Dist: aiohttp>=3.13.3
|
|
43
43
|
Requires-Dist: aiosqlite>=0.22.1
|
|
44
44
|
Requires-Dist: cryptography>=46.0.3
|
|
45
45
|
Requires-Dist: fastapi[standard]>=0.128.0
|
|
@@ -55,9 +55,13 @@ Description-Content-Type: text/markdown
|
|
|
55
55
|
|
|
56
56
|
Load balancer for ChatGPT accounts. Pool multiple accounts, track usage, view everything in a dashboard.
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
### Main Dashboard View
|
|
59
|
+
|
|
60
|
+

|
|
61
|
+
|
|
62
|
+
### Accounts View
|
|
63
|
+
|
|
64
|
+

|
|
61
65
|
|
|
62
66
|
## Quick Start
|
|
63
67
|
|
|
@@ -78,9 +82,7 @@ uvx codex-lb
|
|
|
78
82
|
|
|
79
83
|
Open [localhost:2455](http://localhost:2455) → Add account → Done.
|
|
80
84
|
|
|
81
|
-
## Accounts view
|
|
82
85
|
|
|
83
|
-

|
|
84
86
|
|
|
85
87
|
## Codex CLI & Extension Setup
|
|
86
88
|
|
|
@@ -102,7 +104,31 @@ requires_openai_auth = true # Required: enables model selection in Codex IDE ex
|
|
|
102
104
|
## Data
|
|
103
105
|
|
|
104
106
|
All data stored in `~/.codex-lb/`:
|
|
107
|
+
|
|
105
108
|
- `store.db` – accounts, usage logs
|
|
106
109
|
- `encryption.key` – encrypts tokens (auto-generated)
|
|
107
110
|
|
|
108
111
|
Backup this directory to preserve your accounts.
|
|
112
|
+
|
|
113
|
+
## Contributors ✨
|
|
114
|
+
|
|
115
|
+
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
|
116
|
+
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
117
|
+
<!-- prettier-ignore-start -->
|
|
118
|
+
<!-- markdownlint-disable -->
|
|
119
|
+
<table>
|
|
120
|
+
<tbody>
|
|
121
|
+
<tr>
|
|
122
|
+
<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>
|
|
123
|
+
<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>
|
|
124
|
+
<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>
|
|
125
|
+
</tr>
|
|
126
|
+
</tbody>
|
|
127
|
+
</table>
|
|
128
|
+
|
|
129
|
+
<!-- markdownlint-restore -->
|
|
130
|
+
<!-- prettier-ignore-end -->
|
|
131
|
+
|
|
132
|
+
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
133
|
+
|
|
134
|
+
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
codex_lb-0.3.1/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# codex-lb
|
|
2
|
+
|
|
3
|
+
Load balancer for ChatGPT accounts. Pool multiple accounts, track usage, view everything in a dashboard.
|
|
4
|
+
|
|
5
|
+
### Main Dashboard View
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
### Accounts View
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Docker
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
docker run -d --name codex-lb \
|
|
19
|
+
-p 2455:2455 -p 1455:1455 \
|
|
20
|
+
-v ~/.codex-lb:/var/lib/codex-lb \
|
|
21
|
+
ghcr.io/soju06/codex-lb:latest
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### uvx
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uvx codex-lb
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Open [localhost:2455](http://localhost:2455) → Add account → Done.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Codex CLI & Extension Setup
|
|
35
|
+
|
|
36
|
+
Add to `~/.codex/config.toml`:
|
|
37
|
+
|
|
38
|
+
```toml
|
|
39
|
+
model = "gpt-5.2-codex"
|
|
40
|
+
model_reasoning_effort = "xhigh"
|
|
41
|
+
model_provider = "codex-lb"
|
|
42
|
+
|
|
43
|
+
[model_providers.codex-lb]
|
|
44
|
+
name = "OpenAI" # MUST be "OpenAI" - enables /compact endpoint
|
|
45
|
+
base_url = "http://127.0.0.1:2455/backend-api/codex"
|
|
46
|
+
wire_api = "responses"
|
|
47
|
+
chatgpt_base_url = "http://127.0.0.1:2455"
|
|
48
|
+
requires_openai_auth = true # Required: enables model selection in Codex IDE extension
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Data
|
|
52
|
+
|
|
53
|
+
All data stored in `~/.codex-lb/`:
|
|
54
|
+
|
|
55
|
+
- `store.db` – accounts, usage logs
|
|
56
|
+
- `encryption.key` – encrypts tokens (auto-generated)
|
|
57
|
+
|
|
58
|
+
Backup this directory to preserve your accounts.
|
|
59
|
+
|
|
60
|
+
## Contributors ✨
|
|
61
|
+
|
|
62
|
+
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
|
63
|
+
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
|
64
|
+
<!-- prettier-ignore-start -->
|
|
65
|
+
<!-- markdownlint-disable -->
|
|
66
|
+
<table>
|
|
67
|
+
<tbody>
|
|
68
|
+
<tr>
|
|
69
|
+
<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>
|
|
70
|
+
<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>
|
|
71
|
+
<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>
|
|
72
|
+
</tr>
|
|
73
|
+
</tbody>
|
|
74
|
+
</table>
|
|
75
|
+
|
|
76
|
+
<!-- markdownlint-restore -->
|
|
77
|
+
<!-- prettier-ignore-end -->
|
|
78
|
+
|
|
79
|
+
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
|
80
|
+
|
|
81
|
+
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
|
|
@@ -90,7 +90,17 @@ def claims_from_auth(auth: AuthFile) -> AccountClaims:
|
|
|
90
90
|
)
|
|
91
91
|
|
|
92
92
|
|
|
93
|
+
def generate_unique_account_id(account_id: str | None, email: str | None) -> str:
|
|
94
|
+
if account_id and email and email != DEFAULT_EMAIL:
|
|
95
|
+
email_hash = hashlib.sha256(email.encode()).hexdigest()[:8]
|
|
96
|
+
return f"{account_id}_{email_hash}"
|
|
97
|
+
if account_id:
|
|
98
|
+
return account_id
|
|
99
|
+
return fallback_account_id(email)
|
|
100
|
+
|
|
101
|
+
|
|
93
102
|
def fallback_account_id(email: str | None) -> str:
|
|
103
|
+
"""Generate a fallback account ID when no OpenAI account ID is available."""
|
|
94
104
|
if email and email != DEFAULT_EMAIL:
|
|
95
105
|
digest = hashlib.sha256(email.encode()).hexdigest()[:12]
|
|
96
106
|
return f"email_{digest}"
|
|
@@ -16,6 +16,9 @@ PERMANENT_FAILURE_CODES = {
|
|
|
16
16
|
"account_deleted": "Account has been deleted",
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
SECONDS_PER_DAY = 60 * 60 * 24
|
|
20
|
+
UNKNOWN_RESET_BUCKET_DAYS = 10_000
|
|
21
|
+
|
|
19
22
|
|
|
20
23
|
@dataclass
|
|
21
24
|
class AccountState:
|
|
@@ -24,6 +27,8 @@ class AccountState:
|
|
|
24
27
|
used_percent: float | None = None
|
|
25
28
|
reset_at: float | None = None
|
|
26
29
|
cooldown_until: float | None = None
|
|
30
|
+
secondary_used_percent: float | None = None
|
|
31
|
+
secondary_reset_at: int | None = None
|
|
27
32
|
last_error_at: float | None = None
|
|
28
33
|
last_selected_at: float | None = None
|
|
29
34
|
error_count: int = 0
|
|
@@ -36,7 +41,12 @@ class SelectionResult:
|
|
|
36
41
|
error_message: str | None
|
|
37
42
|
|
|
38
43
|
|
|
39
|
-
def select_account(
|
|
44
|
+
def select_account(
|
|
45
|
+
states: Iterable[AccountState],
|
|
46
|
+
now: float | None = None,
|
|
47
|
+
*,
|
|
48
|
+
prefer_earlier_reset: bool = False,
|
|
49
|
+
) -> SelectionResult:
|
|
40
50
|
current = now or time.time()
|
|
41
51
|
available: list[AccountState] = []
|
|
42
52
|
all_states = list(states)
|
|
@@ -95,18 +105,35 @@ def select_account(states: Iterable[AccountState], now: float | None = None) ->
|
|
|
95
105
|
return SelectionResult(None, f"Rate limit exceeded. Try again in {wait_seconds:.0f}s")
|
|
96
106
|
return SelectionResult(None, "No available accounts")
|
|
97
107
|
|
|
98
|
-
def
|
|
99
|
-
|
|
108
|
+
def _usage_sort_key(state: AccountState) -> tuple[float, float, float, str]:
|
|
109
|
+
primary_used = state.used_percent if state.used_percent is not None else 0.0
|
|
110
|
+
secondary_used = state.secondary_used_percent if state.secondary_used_percent is not None else primary_used
|
|
100
111
|
last_selected = state.last_selected_at or 0.0
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
return secondary_used, primary_used, last_selected, state.account_id
|
|
113
|
+
|
|
114
|
+
def _reset_first_sort_key(state: AccountState) -> tuple[int, float, float, float, str]:
|
|
115
|
+
reset_bucket_days = UNKNOWN_RESET_BUCKET_DAYS
|
|
116
|
+
if state.secondary_reset_at is not None:
|
|
117
|
+
reset_bucket_days = max(
|
|
118
|
+
0,
|
|
119
|
+
int((state.secondary_reset_at - current) // SECONDS_PER_DAY),
|
|
120
|
+
)
|
|
121
|
+
secondary_used, primary_used, last_selected, account_id = _usage_sort_key(state)
|
|
122
|
+
return reset_bucket_days, secondary_used, primary_used, last_selected, account_id
|
|
123
|
+
|
|
124
|
+
selected = min(available, key=_reset_first_sort_key if prefer_earlier_reset else _usage_sort_key)
|
|
104
125
|
return SelectionResult(selected, None)
|
|
105
126
|
|
|
106
127
|
|
|
107
128
|
def handle_rate_limit(state: AccountState, error: UpstreamError) -> None:
|
|
129
|
+
state.status = AccountStatus.RATE_LIMITED
|
|
108
130
|
state.error_count += 1
|
|
109
131
|
state.last_error_at = time.time()
|
|
132
|
+
|
|
133
|
+
reset_at = _extract_reset_at(error)
|
|
134
|
+
if reset_at is not None:
|
|
135
|
+
state.reset_at = reset_at
|
|
136
|
+
|
|
110
137
|
message = error.get("message")
|
|
111
138
|
delay = parse_retry_after(message) if message else None
|
|
112
139
|
if delay is None:
|
|
@@ -40,6 +40,8 @@ class Settings(BaseSettings):
|
|
|
40
40
|
usage_refresh_interval_seconds: int = 60
|
|
41
41
|
encryption_key_file: Path = DEFAULT_ENCRYPTION_KEY_FILE
|
|
42
42
|
database_migrations_fail_fast: bool = True
|
|
43
|
+
log_proxy_request_shape: bool = False
|
|
44
|
+
log_proxy_request_shape_raw_cache_key: bool = False
|
|
43
45
|
|
|
44
46
|
@field_validator("database_url")
|
|
45
47
|
@classmethod
|
|
@@ -17,12 +17,14 @@ from app.db.models import Account
|
|
|
17
17
|
PLAN_CAPACITY_CREDITS_PRIMARY = {
|
|
18
18
|
"plus": 225.0,
|
|
19
19
|
"business": 225.0,
|
|
20
|
+
"team": 225.0,
|
|
20
21
|
"pro": 1500.0,
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
PLAN_CAPACITY_CREDITS_SECONDARY = {
|
|
24
25
|
"plus": 7560.0,
|
|
25
26
|
"business": 7560.0,
|
|
27
|
+
"team": 7560.0,
|
|
26
28
|
"pro": 50400.0,
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -13,6 +13,17 @@ class RequestLogLike(Protocol):
|
|
|
13
13
|
reasoning_tokens: int | None
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def cached_input_tokens_from_log(log: RequestLogLike) -> int | None:
|
|
17
|
+
cached_tokens = log.cached_input_tokens
|
|
18
|
+
if cached_tokens is None:
|
|
19
|
+
return None
|
|
20
|
+
cached_tokens = max(0, int(cached_tokens))
|
|
21
|
+
input_tokens = log.input_tokens
|
|
22
|
+
if input_tokens is not None:
|
|
23
|
+
cached_tokens = min(cached_tokens, int(input_tokens))
|
|
24
|
+
return cached_tokens
|
|
25
|
+
|
|
26
|
+
|
|
16
27
|
def usage_tokens_from_log(log: RequestLogLike) -> UsageTokens | None:
|
|
17
28
|
input_tokens = log.input_tokens
|
|
18
29
|
if input_tokens is None:
|
|
@@ -20,8 +31,7 @@ def usage_tokens_from_log(log: RequestLogLike) -> UsageTokens | None:
|
|
|
20
31
|
output_tokens = log.output_tokens if log.output_tokens is not None else log.reasoning_tokens
|
|
21
32
|
if output_tokens is None:
|
|
22
33
|
return None
|
|
23
|
-
cached_tokens = log
|
|
24
|
-
cached_tokens = max(0, min(cached_tokens, input_tokens))
|
|
34
|
+
cached_tokens = cached_input_tokens_from_log(log) or 0
|
|
25
35
|
return UsageTokens(
|
|
26
36
|
input_tokens=float(input_tokens),
|
|
27
37
|
output_tokens=float(output_tokens),
|
|
@@ -30,8 +30,11 @@ def apply_usage_quota(
|
|
|
30
30
|
reset_at = secondary_reset
|
|
31
31
|
return status, used_percent, reset_at
|
|
32
32
|
if status == AccountStatus.QUOTA_EXCEEDED:
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
if runtime_reset and runtime_reset > time.time():
|
|
34
|
+
reset_at = runtime_reset
|
|
35
|
+
else:
|
|
36
|
+
status = AccountStatus.ACTIVE
|
|
37
|
+
reset_at = None
|
|
35
38
|
elif status == AccountStatus.QUOTA_EXCEEDED and secondary_reset is not None:
|
|
36
39
|
reset_at = secondary_reset
|
|
37
40
|
|
|
@@ -45,8 +48,11 @@ def apply_usage_quota(
|
|
|
45
48
|
reset_at = _fallback_primary_reset(primary_window_minutes) or reset_at
|
|
46
49
|
return status, used_percent, reset_at
|
|
47
50
|
if status == AccountStatus.RATE_LIMITED:
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
if runtime_reset and runtime_reset > time.time():
|
|
52
|
+
reset_at = runtime_reset
|
|
53
|
+
else:
|
|
54
|
+
status = AccountStatus.ACTIVE
|
|
55
|
+
reset_at = None
|
|
50
56
|
|
|
51
57
|
return status, used_percent, reset_at
|
|
52
58
|
|
|
@@ -67,8 +67,9 @@ class UsageCostSummary:
|
|
|
67
67
|
class UsageMetricsSummary:
|
|
68
68
|
requests_7d: int | None
|
|
69
69
|
tokens_secondary_window: int | None
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
cached_tokens_secondary_window: int | None = None
|
|
71
|
+
error_rate_7d: float | None = None
|
|
72
|
+
top_error: str | None = None
|
|
72
73
|
|
|
73
74
|
|
|
74
75
|
@dataclass(frozen=True)
|
|
@@ -8,7 +8,13 @@ from typing import Awaitable, Callable, Final
|
|
|
8
8
|
from sqlalchemy import text
|
|
9
9
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
10
10
|
|
|
11
|
-
from app.db.migrations.versions import
|
|
11
|
+
from app.db.migrations.versions import (
|
|
12
|
+
add_accounts_chatgpt_account_id,
|
|
13
|
+
add_accounts_reset_at,
|
|
14
|
+
add_dashboard_settings,
|
|
15
|
+
add_request_logs_reasoning_effort,
|
|
16
|
+
normalize_account_plan_types,
|
|
17
|
+
)
|
|
12
18
|
|
|
13
19
|
_CREATE_MIGRATIONS_TABLE = """
|
|
14
20
|
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
@@ -21,6 +27,7 @@ _INSERT_MIGRATION = """
|
|
|
21
27
|
INSERT INTO schema_migrations (name, applied_at)
|
|
22
28
|
VALUES (:name, :applied_at)
|
|
23
29
|
ON CONFLICT(name) DO NOTHING
|
|
30
|
+
RETURNING name
|
|
24
31
|
"""
|
|
25
32
|
|
|
26
33
|
|
|
@@ -32,6 +39,10 @@ class Migration:
|
|
|
32
39
|
|
|
33
40
|
MIGRATIONS: Final[tuple[Migration, ...]] = (
|
|
34
41
|
Migration("001_normalize_account_plan_types", normalize_account_plan_types.run),
|
|
42
|
+
Migration("002_add_request_logs_reasoning_effort", add_request_logs_reasoning_effort.run),
|
|
43
|
+
Migration("003_add_accounts_reset_at", add_accounts_reset_at.run),
|
|
44
|
+
Migration("004_add_accounts_chatgpt_account_id", add_accounts_chatgpt_account_id.run),
|
|
45
|
+
Migration("005_add_dashboard_settings", add_dashboard_settings.run),
|
|
35
46
|
)
|
|
36
47
|
|
|
37
48
|
|
|
@@ -54,8 +65,8 @@ async def _apply_migration(session: AsyncSession, migration: Migration) -> bool:
|
|
|
54
65
|
"applied_at": _utcnow_iso(),
|
|
55
66
|
},
|
|
56
67
|
)
|
|
57
|
-
|
|
58
|
-
if
|
|
68
|
+
inserted = result.scalar_one_or_none()
|
|
69
|
+
if inserted is None:
|
|
59
70
|
return False
|
|
60
71
|
await migration.run(session)
|
|
61
72
|
return True
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import text
|
|
4
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def run(session: AsyncSession) -> None:
|
|
8
|
+
bind = session.get_bind()
|
|
9
|
+
dialect = getattr(getattr(bind, "dialect", None), "name", None)
|
|
10
|
+
if dialect == "sqlite":
|
|
11
|
+
await _sqlite_add_column_if_missing(session, "accounts", "chatgpt_account_id", "VARCHAR")
|
|
12
|
+
elif dialect == "postgresql":
|
|
13
|
+
await session.execute(
|
|
14
|
+
text("ALTER TABLE accounts ADD COLUMN IF NOT EXISTS chatgpt_account_id VARCHAR"),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def _sqlite_add_column_if_missing(
|
|
19
|
+
session: AsyncSession,
|
|
20
|
+
table: str,
|
|
21
|
+
column: str,
|
|
22
|
+
column_type: str,
|
|
23
|
+
) -> None:
|
|
24
|
+
result = await session.execute(text(f"PRAGMA table_info({table})"))
|
|
25
|
+
rows = result.fetchall()
|
|
26
|
+
existing = {row[1] for row in rows if len(row) > 1}
|
|
27
|
+
if column in existing:
|
|
28
|
+
return
|
|
29
|
+
await session.execute(text(f"ALTER TABLE {table} ADD COLUMN {column} {column_type}"))
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import text
|
|
4
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def run(session: AsyncSession) -> None:
|
|
8
|
+
bind = session.get_bind()
|
|
9
|
+
dialect = getattr(getattr(bind, "dialect", None), "name", None)
|
|
10
|
+
if dialect == "sqlite":
|
|
11
|
+
await _sqlite_add_column_if_missing(session, "accounts", "reset_at", "INTEGER")
|
|
12
|
+
elif dialect == "postgresql":
|
|
13
|
+
await session.execute(
|
|
14
|
+
text("ALTER TABLE accounts ADD COLUMN IF NOT EXISTS reset_at INTEGER"),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def _sqlite_add_column_if_missing(
|
|
19
|
+
session: AsyncSession,
|
|
20
|
+
table: str,
|
|
21
|
+
column: str,
|
|
22
|
+
column_type: str,
|
|
23
|
+
) -> None:
|
|
24
|
+
result = await session.execute(text(f"PRAGMA table_info({table})"))
|
|
25
|
+
rows = result.fetchall()
|
|
26
|
+
existing = {row[1] for row in rows if len(row) > 1}
|
|
27
|
+
if column in existing:
|
|
28
|
+
return
|
|
29
|
+
await session.execute(text(f"ALTER TABLE {table} ADD COLUMN {column} {column_type}"))
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from sqlalchemy import inspect
|
|
4
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
5
|
+
from sqlalchemy.orm import Session
|
|
6
|
+
|
|
7
|
+
from app.db.models import DashboardSettings
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _settings_table_exists(session: Session) -> bool:
|
|
11
|
+
inspector = inspect(session.connection())
|
|
12
|
+
return inspector.has_table("dashboard_settings")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def run(session: AsyncSession) -> None:
|
|
16
|
+
exists = await session.run_sync(_settings_table_exists)
|
|
17
|
+
if not exists:
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
row = await session.get(DashboardSettings, 1)
|
|
21
|
+
if row is not None:
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
session.add(
|
|
25
|
+
DashboardSettings(
|
|
26
|
+
id=1,
|
|
27
|
+
sticky_threads_enabled=False,
|
|
28
|
+
prefer_earlier_reset_accounts=False,
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
await session.flush()
|