codex-lb 0.2.0__py3-none-any.whl → 0.3.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.
Files changed (44) hide show
  1. app/core/auth/__init__.py +10 -0
  2. app/core/balancer/logic.py +33 -6
  3. app/core/config/settings.py +2 -0
  4. app/core/usage/__init__.py +2 -0
  5. app/core/usage/logs.py +12 -2
  6. app/core/usage/quota.py +10 -4
  7. app/core/usage/types.py +3 -2
  8. app/db/migrations/__init__.py +14 -3
  9. app/db/migrations/versions/add_accounts_chatgpt_account_id.py +29 -0
  10. app/db/migrations/versions/add_accounts_reset_at.py +29 -0
  11. app/db/migrations/versions/add_dashboard_settings.py +31 -0
  12. app/db/migrations/versions/add_request_logs_reasoning_effort.py +21 -0
  13. app/db/models.py +33 -0
  14. app/db/session.py +71 -11
  15. app/dependencies.py +27 -1
  16. app/main.py +11 -2
  17. app/modules/accounts/auth_manager.py +44 -3
  18. app/modules/accounts/repository.py +14 -6
  19. app/modules/accounts/service.py +4 -2
  20. app/modules/oauth/service.py +4 -3
  21. app/modules/proxy/load_balancer.py +74 -5
  22. app/modules/proxy/service.py +155 -31
  23. app/modules/proxy/sticky_repository.py +56 -0
  24. app/modules/request_logs/repository.py +6 -3
  25. app/modules/request_logs/schemas.py +2 -0
  26. app/modules/request_logs/service.py +8 -1
  27. app/modules/settings/__init__.py +1 -0
  28. app/modules/settings/api.py +37 -0
  29. app/modules/settings/repository.py +40 -0
  30. app/modules/settings/schemas.py +13 -0
  31. app/modules/settings/service.py +33 -0
  32. app/modules/shared/schemas.py +16 -2
  33. app/modules/usage/schemas.py +1 -0
  34. app/modules/usage/service.py +17 -1
  35. app/modules/usage/updater.py +36 -7
  36. app/static/7.css +73 -0
  37. app/static/index.css +33 -4
  38. app/static/index.html +51 -4
  39. app/static/index.js +231 -25
  40. {codex_lb-0.2.0.dist-info → codex_lb-0.3.0.dist-info}/METADATA +2 -2
  41. {codex_lb-0.2.0.dist-info → codex_lb-0.3.0.dist-info}/RECORD +44 -34
  42. {codex_lb-0.2.0.dist-info → codex_lb-0.3.0.dist-info}/WHEEL +0 -0
  43. {codex_lb-0.2.0.dist-info → codex_lb-0.3.0.dist-info}/entry_points.txt +0 -0
  44. {codex_lb-0.2.0.dist-info → codex_lb-0.3.0.dist-info}/licenses/LICENSE +0 -0
app/static/index.js CHANGED
@@ -16,6 +16,7 @@
16
16
  oauthStart: "/api/oauth/start",
17
17
  oauthStatus: "/api/oauth/status",
18
18
  oauthComplete: "/api/oauth/complete",
19
+ settings: "/api/settings",
19
20
  };
20
21
 
21
22
  const PAGES = [
@@ -33,6 +34,13 @@
33
34
  title: "Codex Load Balancer - Accounts",
34
35
  path: "/accounts",
35
36
  },
37
+ {
38
+ id: "settings",
39
+ tabId: "tab-settings",
40
+ label: "Settings",
41
+ title: "Codex Load Balancer - Settings",
42
+ path: "/settings",
43
+ },
36
44
  ];
37
45
 
38
46
  const BASE_PATH = "/dashboard";
@@ -187,6 +195,7 @@
187
195
  metrics: {
188
196
  requests7d: 0,
189
197
  tokensSecondaryWindow: null,
198
+ cachedTokensSecondaryWindow: null,
190
199
  cost7d: 0,
191
200
  errorRate7d: null,
192
201
  topError: "",
@@ -242,6 +251,40 @@
242
251
  return compactFormatter.format(numeric);
243
252
  };
244
253
 
254
+ const formatTokensWithCached = (totalTokens, cachedInputTokens) => {
255
+ const total = toNumber(totalTokens);
256
+ if (total === null) {
257
+ return "--";
258
+ }
259
+ const cached = toNumber(cachedInputTokens);
260
+ if (cached === null || cached <= 0) {
261
+ return formatCompactNumber(total);
262
+ }
263
+ return `${formatCompactNumber(total)} (${formatCompactNumber(cached)} Cached)`;
264
+ };
265
+
266
+ const formatCachedTokensMeta = (totalTokens, cachedInputTokens) => {
267
+ const total = toNumber(totalTokens);
268
+ const cached = toNumber(cachedInputTokens);
269
+ if (total === null || total <= 0 || cached === null || cached <= 0) {
270
+ return "Cached: --";
271
+ }
272
+ const percent = Math.min(100, Math.max(0, (cached / total) * 100));
273
+ return `Cached: ${formatCompactNumber(cached)} (${Math.round(percent)}%)`;
274
+ };
275
+
276
+ const formatModelLabel = (model, reasoningEffort) => {
277
+ const base = (model || "").trim();
278
+ if (!base) {
279
+ return "--";
280
+ }
281
+ const effort = (reasoningEffort || "").trim();
282
+ if (!effort) {
283
+ return base;
284
+ }
285
+ return `${base} (${effort})`;
286
+ };
287
+
245
288
  const formatCurrency = (value) => {
246
289
  const numeric = toNumber(value);
247
290
  if (numeric === null) {
@@ -565,8 +608,10 @@
565
608
  accountId: entry.accountId,
566
609
  requestId: entry.requestId,
567
610
  model: entry.model,
611
+ reasoningEffort: entry.reasoningEffort ?? null,
568
612
  status: entry.status,
569
613
  tokens: toNumber(entry.tokens),
614
+ cachedInputTokens: toNumber(entry.cachedInputTokens),
570
615
  cost: toNumber(entry.costUsd),
571
616
  errorCode: entry.errorCode ?? null,
572
617
  errorMessage: entry.errorMessage ?? null,
@@ -595,17 +640,21 @@
595
640
  const secondaryRemainingPercent = toNumber(
596
641
  secondaryRow?.remainingPercentAvg,
597
642
  );
643
+ const mergedSecondaryRemaining =
644
+ secondaryRemainingPercent ??
645
+ account.usage?.secondaryRemainingPercent ??
646
+ 0;
647
+ const mergedPrimaryRemaining =
648
+ primaryRemainingPercent ??
649
+ account.usage?.primaryRemainingPercent ??
650
+ 0;
651
+ const effectivePrimaryRemaining =
652
+ mergedSecondaryRemaining <= 0 ? 0 : mergedPrimaryRemaining;
598
653
  return {
599
654
  ...account,
600
655
  usage: {
601
- primaryRemainingPercent:
602
- primaryRemainingPercent ??
603
- account.usage?.primaryRemainingPercent ??
604
- 0,
605
- secondaryRemainingPercent:
606
- secondaryRemainingPercent ??
607
- account.usage?.secondaryRemainingPercent ??
608
- 0,
656
+ primaryRemainingPercent: effectivePrimaryRemaining,
657
+ secondaryRemainingPercent: mergedSecondaryRemaining,
609
658
  },
610
659
  resetAtPrimary: account.resetAtPrimary ?? null,
611
660
  resetAtSecondary: account.resetAtSecondary ?? null,
@@ -641,6 +690,7 @@
641
690
  accountId: entry.accountId,
642
691
  capacityCredits: toNumber(entry.capacityCredits) || 0,
643
692
  remainingCredits: toNumber(entry.remainingCredits) || 0,
693
+ remainingPercentAvg: toNumber(entry.remainingPercentAvg),
644
694
  })),
645
695
  };
646
696
  };
@@ -654,6 +704,7 @@
654
704
  const metrics = summary?.metrics || {};
655
705
  const requests7d = toNumber(metrics.requests7d) ?? 0;
656
706
  const tokensSecondaryWindow = toNumber(metrics.tokensSecondaryWindow);
707
+ const cachedTokensSecondaryWindow = toNumber(metrics.cachedTokensSecondaryWindow);
657
708
  const errorRate7d = toNumber(metrics.errorRate7d);
658
709
  const topError = metrics.topError || "";
659
710
  return {
@@ -665,6 +716,7 @@
665
716
  metrics: {
666
717
  requests7d,
667
718
  tokensSecondaryWindow,
719
+ cachedTokensSecondaryWindow,
668
720
  cost7d: toNumber(summary?.cost?.totalUsd7d) || 0,
669
721
  errorRate7d,
670
722
  topError,
@@ -699,7 +751,7 @@
699
751
  return acc;
700
752
  }, {});
701
753
 
702
- const buildRemainingItems = (entries, accounts, capacity) => {
754
+ const buildRemainingItems = (entries, accounts, capacity, windowKey) => {
703
755
  const accountMap = new Map(
704
756
  (accounts || []).map((account) => [account.id, account]),
705
757
  );
@@ -707,7 +759,23 @@
707
759
  const account = accountMap.get(entry.accountId);
708
760
  const label = account ? account.email : entry.accountId;
709
761
  const value = toNumber(entry.remainingCredits) || 0;
710
- const rawPercent = capacity > 0 ? (value / capacity) * 100 : 0;
762
+ const percentFromApi = toNumber(entry.remainingPercentAvg);
763
+ const percentFromAccount =
764
+ windowKey === "primary"
765
+ ? toNumber(account?.usage?.primaryRemainingPercent)
766
+ : windowKey === "secondary"
767
+ ? toNumber(account?.usage?.secondaryRemainingPercent)
768
+ : null;
769
+ const entryCapacity = toNumber(entry.capacityCredits) || 0;
770
+ const denominator = entryCapacity > 0 ? entryCapacity : capacity;
771
+ const rawPercent =
772
+ percentFromApi !== null
773
+ ? percentFromApi
774
+ : percentFromAccount !== null
775
+ ? percentFromAccount
776
+ : denominator > 0
777
+ ? (value / denominator) * 100
778
+ : 0;
711
779
  const remainingPercent = Math.min(100, Math.max(0, rawPercent));
712
780
  return {
713
781
  accountId: entry.accountId,
@@ -734,6 +802,38 @@
734
802
  }));
735
803
  };
736
804
 
805
+ const buildSecondaryExhaustedIndex = (accounts) => {
806
+ const exhausted = new Set();
807
+ (accounts || []).forEach((account) => {
808
+ const remaining = toNumber(account?.usage?.secondaryRemainingPercent);
809
+ if (remaining !== null && remaining <= 0 && account?.id) {
810
+ exhausted.add(account.id);
811
+ }
812
+ });
813
+ return exhausted;
814
+ };
815
+
816
+ const applySecondaryExhaustedToPrimary = (entries, exhaustedIds) => {
817
+ if (!entries?.length || !exhaustedIds?.size) {
818
+ return entries || [];
819
+ }
820
+ return entries.map((entry) => {
821
+ if (entry?.accountId && exhaustedIds.has(entry.accountId)) {
822
+ return {
823
+ ...entry,
824
+ remainingCredits: 0,
825
+ };
826
+ }
827
+ return entry;
828
+ });
829
+ };
830
+
831
+ const sumRemainingCredits = (entries) =>
832
+ (entries || []).reduce(
833
+ (acc, entry) => acc + (toNumber(entry?.remainingCredits) || 0),
834
+ 0,
835
+ );
836
+
737
837
  const buildDonutGradient = (items, total) => {
738
838
  if (!items.length || total <= 0) {
739
839
  return `conic-gradient(${CONSUMED_COLOR} 0 100%)`;
@@ -776,6 +876,7 @@
776
876
  const statusCounts = countByStatus(accounts);
777
877
  const secondaryWindowMinutes =
778
878
  state.dashboardData.usage?.secondary?.windowMinutes ?? null;
879
+ const secondaryExhaustedAccounts = buildSecondaryExhaustedIndex(accounts);
779
880
 
780
881
  const badges = ["active", "paused", "limited", "exceeded", "deactivated"]
781
882
  .map((status) => {
@@ -795,7 +896,10 @@
795
896
  {
796
897
  title: `Tokens (${formatWindowLabel("secondary", secondaryWindowMinutes)})`,
797
898
  value: formatCompactNumber(metrics.tokensSecondaryWindow),
798
- meta: "Scope: responses",
899
+ meta: formatCachedTokensMeta(
900
+ metrics.tokensSecondaryWindow,
901
+ metrics.cachedTokensSecondaryWindow,
902
+ ),
799
903
  },
800
904
  {
801
905
  title: "Cost (7d)",
@@ -823,18 +927,37 @@
823
927
  resetAt: null,
824
928
  byAccount: [],
825
929
  };
826
- const remaining = toNumber(usage.remaining) || 0;
930
+ const rawEntries = usage.byAccount || [];
931
+ const hasPrimaryAdjustments =
932
+ window.key === "primary" &&
933
+ rawEntries.some(
934
+ (entry) =>
935
+ entry?.accountId && secondaryExhaustedAccounts.has(entry.accountId),
936
+ );
937
+ const entries =
938
+ window.key === "primary"
939
+ ? applySecondaryExhaustedToPrimary(
940
+ rawEntries,
941
+ secondaryExhaustedAccounts,
942
+ )
943
+ : rawEntries;
944
+ const remaining =
945
+ hasPrimaryAdjustments
946
+ ? sumRemainingCredits(entries)
947
+ : toNumber(usage.remaining) || 0;
827
948
  const capacity = Math.max(remaining, toNumber(usage.capacity) || 0);
828
949
  const consumed = Math.max(0, capacity - remaining);
829
950
  const items = buildRemainingItems(
830
- usage.byAccount || [],
951
+ entries,
831
952
  accounts,
832
953
  capacity,
954
+ window.key,
833
955
  );
834
956
  const gradient = buildDonutGradient(items, capacity);
835
957
  const legendItems = items.map((item) => ({
836
958
  label: item.label,
837
- detail: `Remaining ${formatPercent(item.remainingPercent)}`,
959
+ detailLabel: "Remaining",
960
+ detailValue: formatPercent(item.remainingPercent),
838
961
  color: item.color,
839
962
  }));
840
963
  if (capacity > 0 && consumed > 0) {
@@ -844,7 +967,8 @@
844
967
  );
845
968
  legendItems.push({
846
969
  label: "Consumed",
847
- detail: `${formatPercent(consumedPercent)}`,
970
+ detailLabel: "",
971
+ detailValue: formatPercent(consumedPercent),
848
972
  color: CONSUMED_COLOR,
849
973
  });
850
974
  }
@@ -884,17 +1008,18 @@
884
1008
  const requests = state.dashboardData.recentRequests.map((request) => {
885
1009
  const rawError = request.errorMessage || request.errorCode || "";
886
1010
  const accountLabel = formatAccountLabel(request.accountId, accounts);
1011
+ const modelLabel = formatModelLabel(request.model, request.reasoningEffort);
887
1012
  return {
888
1013
  key: `${request.requestId}-${request.timestamp}`,
889
1014
  requestId: request.requestId,
890
1015
  time: formatTimeLong(request.timestamp),
891
1016
  account: accountLabel,
892
- model: request.model,
1017
+ model: modelLabel,
893
1018
  status: {
894
1019
  class: requestStatusClass(request.status),
895
1020
  label: requestStatusLabel(request.status),
896
1021
  },
897
- tokens: formatNumber(request.tokens),
1022
+ tokens: formatTokensWithCached(request.tokens, request.cachedInputTokens),
898
1023
  cost: formatCurrency(request.cost),
899
1024
  error: rawError ? truncateText(rawError, 80) : "--",
900
1025
  errorTitle: rawError,
@@ -1031,6 +1156,20 @@
1031
1156
  return responsePayload;
1032
1157
  };
1033
1158
 
1159
+ const putJson = async (url, payload, label) => {
1160
+ const response = await fetch(url, {
1161
+ method: "PUT",
1162
+ headers: { "Content-Type": "application/json" },
1163
+ body: JSON.stringify(payload || {}),
1164
+ });
1165
+ const responsePayload = await readResponsePayload(response);
1166
+ if (!response.ok) {
1167
+ const message = extractErrorMessage(responsePayload);
1168
+ throw new Error(message || `Failed to ${label} (${response.status})`);
1169
+ }
1170
+ return responsePayload;
1171
+ };
1172
+
1034
1173
  const deleteJson = async (url, label) => {
1035
1174
  const response = await fetch(url, { method: "DELETE" });
1036
1175
  const responsePayload = await readResponsePayload(response);
@@ -1055,6 +1194,16 @@
1055
1194
  const fetchRequestLogs = async (limit) =>
1056
1195
  fetchJson(API_ENDPOINTS.requestLogs(limit), "request logs");
1057
1196
 
1197
+ const normalizeSettingsPayload = (payload) => ({
1198
+ stickyThreadsEnabled: Boolean(payload?.stickyThreadsEnabled),
1199
+ preferEarlierResetAccounts: Boolean(payload?.preferEarlierResetAccounts),
1200
+ });
1201
+
1202
+ const fetchSettings = async () => {
1203
+ const payload = await fetchJson(API_ENDPOINTS.settings, "settings");
1204
+ return normalizeSettingsPayload(payload);
1205
+ };
1206
+
1058
1207
  const registerApp = () => {
1059
1208
  Alpine.data("feApp", () => ({
1060
1209
  view: "dashboard",
@@ -1063,6 +1212,11 @@
1063
1212
  ui: createUiConfig(),
1064
1213
  dashboardData: createEmptyDashboardData(),
1065
1214
  dashboard: createEmptyDashboardView(),
1215
+ settings: {
1216
+ stickyThreadsEnabled: false,
1217
+ preferEarlierResetAccounts: false,
1218
+ isSaving: false,
1219
+ },
1066
1220
  accounts: {
1067
1221
  selectedId: "",
1068
1222
  rows: [],
@@ -1158,12 +1312,14 @@
1158
1312
  primaryResult,
1159
1313
  secondaryResult,
1160
1314
  requestLogsResult,
1315
+ settingsResult,
1161
1316
  ] = await Promise.allSettled([
1162
1317
  fetchAccounts(),
1163
1318
  fetchUsageSummary(),
1164
1319
  fetchUsageWindow("primary"),
1165
1320
  fetchUsageWindow("secondary"),
1166
1321
  fetchRequestLogs(50),
1322
+ fetchSettings(),
1167
1323
  ]);
1168
1324
  const errors = [];
1169
1325
  if (accountsResult.status !== "fulfilled") {
@@ -1196,6 +1352,12 @@
1196
1352
  errors.push(requestLogsResult.reason);
1197
1353
  }
1198
1354
 
1355
+ const settings =
1356
+ settingsResult.status === "fulfilled" ? settingsResult.value : null;
1357
+ if (settingsResult.status === "rejected") {
1358
+ errors.push(settingsResult.reason);
1359
+ }
1360
+
1199
1361
  const mergedAccounts = mergeUsageIntoAccounts(
1200
1362
  accountsResult.value,
1201
1363
  primaryUsage,
@@ -1208,6 +1370,7 @@
1208
1370
  primaryUsage,
1209
1371
  secondaryUsage,
1210
1372
  requestLogs,
1373
+ settings,
1211
1374
  },
1212
1375
  preferredId,
1213
1376
  );
@@ -1252,11 +1415,54 @@
1252
1415
  primaryUsage: data.primaryUsage,
1253
1416
  secondaryUsage: data.secondaryUsage,
1254
1417
  requestLogs: data.requestLogs,
1418
+ settings: data.settings,
1255
1419
  });
1420
+ if (data.settings) {
1421
+ this.settings.stickyThreadsEnabled = Boolean(
1422
+ data.settings.stickyThreadsEnabled,
1423
+ );
1424
+ this.settings.preferEarlierResetAccounts = Boolean(
1425
+ data.settings.preferEarlierResetAccounts,
1426
+ );
1427
+ }
1256
1428
  this.ui.usageWindows = buildUsageWindowConfig(data.summary);
1257
1429
  this.dashboard = buildDashboardView(this);
1258
1430
  this.syncAccountSearchSelection();
1259
1431
  },
1432
+ async saveSettings() {
1433
+ if (this.settings.isSaving) {
1434
+ return;
1435
+ }
1436
+ this.settings.isSaving = true;
1437
+ try {
1438
+ const payload = {
1439
+ stickyThreadsEnabled: this.settings.stickyThreadsEnabled,
1440
+ preferEarlierResetAccounts: this.settings.preferEarlierResetAccounts,
1441
+ };
1442
+ const updated = await putJson(
1443
+ API_ENDPOINTS.settings,
1444
+ payload,
1445
+ "save settings",
1446
+ );
1447
+ const normalized = normalizeSettingsPayload(updated);
1448
+ this.settings.stickyThreadsEnabled = normalized.stickyThreadsEnabled;
1449
+ this.settings.preferEarlierResetAccounts =
1450
+ normalized.preferEarlierResetAccounts;
1451
+ this.openMessageBox({
1452
+ tone: "success",
1453
+ title: "Settings saved",
1454
+ message: "Routing settings updated.",
1455
+ });
1456
+ } catch (error) {
1457
+ this.openMessageBox({
1458
+ tone: "error",
1459
+ title: "Settings save failed",
1460
+ message: error.message || "Failed to save settings.",
1461
+ });
1462
+ } finally {
1463
+ this.settings.isSaving = false;
1464
+ }
1465
+ },
1260
1466
  focusAccountSearch() {
1261
1467
  this.$refs.accountSearch?.focus();
1262
1468
  },
@@ -1693,15 +1899,15 @@
1693
1899
  const items =
1694
1900
  this.view === "accounts"
1695
1901
  ? [
1696
- `Selection: ${this.accounts.selectedId || "--"}`,
1697
- `Rotation: ${this.dashboardData.routing?.rotationEnabled ? "enabled" : "disabled"}`,
1698
- `Last sync: ${lastSync}`,
1699
- ]
1902
+ `Selection: ${this.accounts.selectedId || "--"}`,
1903
+ `Rotation: ${this.dashboardData.routing?.rotationEnabled ? "enabled" : "disabled"}`,
1904
+ `Last sync: ${lastSync}`,
1905
+ ]
1700
1906
  : [
1701
- `Last sync: ${lastSync}`,
1702
- `Routing: ${routingLabel(this.dashboardData.routing?.strategy)}`,
1703
- `Backend: ${this.backendPath}`,
1704
- ];
1907
+ `Last sync: ${lastSync}`,
1908
+ `Routing: ${routingLabel(this.dashboardData.routing?.strategy)}`,
1909
+ `Backend: ${this.backendPath}`,
1910
+ ];
1705
1911
  if (this.importState.isLoading) {
1706
1912
  items.unshift(
1707
1913
  `Importing ${this.importState.fileName || "auth.json"}...`,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-lb
3
- Version: 0.2.0
3
+ Version: 0.3.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>
@@ -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.2
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
@@ -1,17 +1,17 @@
1
1
  app/__init__.py,sha256=uqZSnn_VEL8TIUxsYqdf4zA2ByJYfjA06fArVAzrHFo,89
2
2
  app/cli.py,sha256=gkIAkYOT9SbQjUDnVmwhVKZeKjL3YJCMrOjFINwBx54,544
3
- app/dependencies.py,sha256=UfxnxyoYih6TOfGGnptJnBSojg7BF31JjA5XyvcXS7o,3576
4
- app/main.py,sha256=qr55Cm336acBUDnBrWZ64gtOI4xImo_8FeuVfDtyIP4,4349
3
+ app/dependencies.py,sha256=kfB_TxeZve_cnBxhOHZezKwkOwTNFwoHzNCJDUkGJD8,4377
4
+ app/main.py,sha256=wCELolhaBfJCEfFfSELNTQ5hZ1Xo-G5Wa3vMkIOY6gk,4665
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
8
8
  app/core/plan_types.py,sha256=6if0Zj2sTrtvpijkiJ0AtfkY0EfwARjKQetcD7Q8DM8,1548
9
9
  app/core/types.py,sha256=cVWOt-cYNjleEBvPYQVOO-tru38mZYsrBrlYRUfXPxg,208
10
- app/core/auth/__init__.py,sha256=r5YdeosInCIftl6whdxsWIYick2wp0o716wN5HLI4y4,2841
10
+ app/core/auth/__init__.py,sha256=5BmHcd8P05YmMpnKAxokWVkOkZK2Opaf04qgEk9P8k4,3257
11
11
  app/core/auth/models.py,sha256=iyygu0uHK7fu8txyIyCkXmlgqYPbqEZu3IFysD1t-gk,1557
12
12
  app/core/auth/refresh.py,sha256=jhYxT2mQFX4CCvuBQfYNcI53iOtWa0vSkTuZAfbpd9k,5105
13
13
  app/core/balancer/__init__.py,sha256=bGy7gITRPObt6P9NdG3pg8t3qWoSW0XWoADqfNYrhdg,405
14
- app/core/balancer/logic.py,sha256=W6Mmg_KUszoYx_DynrzmT4k0OHwbPb5tr-ng1a--GO4,5623
14
+ app/core/balancer/logic.py,sha256=N8skZL7ye_qLtUMBcHjgqSjsuCKWFOPdBposu_DLxuc,6730
15
15
  app/core/balancer/types.py,sha256=gDgjlTy-NH3YhHYl2-YYpIabnckN9Q8-4cRy6S1u0K4,191
16
16
  app/core/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  app/core/clients/http.py,sha256=yfFwsaIZNbSmNtyV02WnMKn00JdoNNSlSIx5xB3QY4o,987
@@ -19,69 +19,79 @@ app/core/clients/oauth.py,sha256=XgAzQVAMudBUCQ9nGKUC9N7zagSiawBdhIVmxf9HHwQ,118
19
19
  app/core/clients/proxy.py,sha256=ZWs0TBYcOgDz_jZ3tucDsRiqQ_9TGxU1oPGiI1mu-bE,9378
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=LZukDTD4ILfq2CjyF89qxk9s4Lu-qqzotL4e61z0FXo,2550
22
+ app/core/config/settings.py,sha256=h6JoHfsaqZpUmv8RPKw38zv45eHyjv53PbHCw3pK7h8,2648
23
23
  app/core/openai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  app/core/openai/models.py,sha256=SzVPqmp9IDbJTW6gLGHeLxrpAmFehJPSC4fVsqzEmQA,3344
25
25
  app/core/openai/parsing.py,sha256=VuE1OyPAv1umrSbkzqa6dcjPYfk00isOVB3O0xUPLBw,1537
26
26
  app/core/openai/requests.py,sha256=Nnd4ZtxUtXP-jJ7LGKpPf_JDBIFo-X9k0qMw9SyANTc,1675
27
- app/core/usage/__init__.py,sha256=q8rz-XwZhIZ-hsmPmXB9JveqyP46tusLUR39c1cC75E,5687
28
- app/core/usage/logs.py,sha256=nQP6208cmsawCre0s7ekadyZiXofNqz18_3xBeuku-Q,1812
27
+ app/core/usage/__init__.py,sha256=8SBpiClJR2wl653Cj1DTvmVUvi2jzU6kKXJtq91ofmE,5726
28
+ app/core/usage/logs.py,sha256=8TrE9nccEjIJslMfAk8AJLmX_Zzeksu8qMTvrGwePx0,2129
29
29
  app/core/usage/models.py,sha256=FtBQx4Rb7jpwcqxmGXxg7RTVV17LK1QOSWaJIkaaNoQ,878
30
30
  app/core/usage/pricing.py,sha256=6p8rJ26Gk61mz2t_h9sa0T7NiPDUTiNpzoDewMzT6E0,5464
31
- app/core/usage/quota.py,sha256=Xwz6V_qjT8AcD8vHUv0Qxbrzve3pE92PRB_kAGU_oRI,1977
32
- app/core/usage/types.py,sha256=CbFF6JYSLvALa2P0qYuj9J9b_w53Fgjg0JOu4RzMWbI,2104
31
+ app/core/usage/quota.py,sha256=rM6p5NshxrAO5sXcSIE69cFY8ChQXfa-_9WnjAI1uMU,2235
32
+ app/core/usage/types.py,sha256=EbZQjJuh2y2cMztRYf-Qe15AkPQd08LLu7WSlC4PWTY,2172
33
33
  app/core/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  app/core/utils/request_id.py,sha256=gxafI-Se8dRQTir-HNGelMzC9S2gwYZntTkVzEqzp_I,705
35
35
  app/core/utils/retry.py,sha256=UmBap1Wh-CBT7r4fHzVb_PI9-LR9-HjUtDzRnhRjP2U,822
36
36
  app/core/utils/sse.py,sha256=DJMOU4vW5Ir_4WeL5t5t7i33aRMnqVPU0eQvGn4sBv8,537
37
37
  app/core/utils/time.py,sha256=B6FfSe43Eq_puE6eourly1X3gajyihK2VOAwJ8M3wyI,497
38
38
  app/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- app/db/models.py,sha256=JCknQCuzjHfgyuSzjHqBmeIE-0XisIAQGEhEWqmzabs,3841
40
- app/db/session.py,sha256=1e8ATHwgnPRuZvULd0DxFTS6oVoRfLoCCY3UerePOsU,2068
41
- app/db/migrations/__init__.py,sha256=LIvASH5r6U75B8-RmS2K1mGCmQ09PV2WqCvtyk9ACDQ,2152
39
+ app/db/models.py,sha256=Wp7OUMYtqkT-8BD-aoYUsdZgTCMMXg-e0VU1vSKMRg8,5244
40
+ app/db/session.py,sha256=T_UdGcqzn5YXo-fuRW8s5xJQvOuJsV4eTSZE0_V2C44,3910
41
+ app/db/migrations/__init__.py,sha256=hRZVLgAQfP-okuhIiKkQtwfnPtBvYXcLJTqg0Yex1Vc,2633
42
42
  app/db/migrations/versions/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
43
+ app/db/migrations/versions/add_accounts_chatgpt_account_id.py,sha256=5QHfko2f7dq0G34Cqe8CkcKbQor2W5LegfnVu_HgZN8,978
44
+ app/db/migrations/versions/add_accounts_reset_at.py,sha256=cO4Z4LU82JZADkBkYygxzkKdZrsNhGp899zPEtUBvtk,958
45
+ app/db/migrations/versions/add_dashboard_settings.py,sha256=SA6GpVU2-jStSyzKK1osLCTO_WKr4WF58fjNdP900Ec,778
46
+ app/db/migrations/versions/add_request_logs_reasoning_effort.py,sha256=jBy4rH5R2yCAzo5N7KbiA3tbjsoxxDjAy95Poe_qen4,771
43
47
  app/db/migrations/versions/normalize_account_plan_types.py,sha256=WpPhkJ2gtpgw5mbErj2He9ahjeqNV3Z7c9RmmMjMmtM,575
44
48
  app/modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
49
  app/modules/accounts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
50
  app/modules/accounts/api.py,sha256=rSkYrg3_p_JE1kHZ4xXa5lBFTdT40v1oNAmlTDGOguY,2807
47
- app/modules/accounts/auth_manager.py,sha256=Sj_nuL663x0Rv1QZiGNJDDz7MvhMZF65ksXWGNzK53Q,2996
48
- app/modules/accounts/repository.py,sha256=R9-9OzIDe2aDwLdhTHUCWXdOavN4nVxAeNMOv1l_ROU,3010
51
+ app/modules/accounts/auth_manager.py,sha256=YUKdeHbszRLYZ4uRQzvk6ICk2cB73LjlJMEiWphBWWw,4682
52
+ app/modules/accounts/repository.py,sha256=xOj47_BeYzVPj_WBejrlGtlTDd0tNR4o6sZIrM8QriE,3382
49
53
  app/modules/accounts/schemas.py,sha256=gtlbPg5uxM3t_V5JxCL6eP-UaU6TSE0UoX2yIpxM_a0,1659
50
- app/modules/accounts/service.py,sha256=Wh-npKQjMREFnt29RQkwWEjf6Dg_2PQYdj0iwGTVvSg,8874
54
+ app/modules/accounts/service.py,sha256=M3_SRD21yp0YN820IHdqNIlZsynNsSUN6TO6RcESDeI,8973
51
55
  app/modules/health/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
56
  app/modules/health/api.py,sha256=CSs09qeEy0DjM9twmmQGg-kNt2J4XNFBX72CwLuK-Gs,297
53
57
  app/modules/health/schemas.py,sha256=nnF32SQbR-5rLSaRtwwiC3ZOhjSnRHPgr2cagwW2rqo,177
54
58
  app/modules/oauth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
59
  app/modules/oauth/api.py,sha256=Wu_DEXgN3hP9Si6MMkl6-0v_Blwjahqwtun1iP7DjVk,1877
56
60
  app/modules/oauth/schemas.py,sha256=sdDKP7u9bO87lcZXjK7uSokurHPS22nN2p_jkw9iEBc,777
57
- app/modules/oauth/service.py,sha256=NWpz_GP6_VuXdStZ4qYjiotqxn4FXZkSRlrguPznTPY,12841
61
+ app/modules/oauth/service.py,sha256=fEzUtoq1g-20NgWZlB_maA2XB-scMa0HJV24SxZPnqQ,12908
58
62
  app/modules/oauth/templates/oauth_success.html,sha256=YNSGUIozcZEJQjpFtM2sgF4n8jqfbmx8LRwdXTraym4,3799
59
63
  app/modules/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
64
  app/modules/proxy/api.py,sha256=BR_qNlNZg2Ft5GYQ-7AzlLlJFf6RVJmlzJzFr_KHUIc,2921
61
65
  app/modules/proxy/helpers.py,sha256=-XVTNTpkDvJ3KfsuGHztfUVBGBFm2H-AaNTNH2dNe6o,8739
62
- app/modules/proxy/load_balancer.py,sha256=ti3SS3A-nXYFSaJSo9L4-1QrYpBt4UbB3gZfBpJQ97U,6898
66
+ app/modules/proxy/load_balancer.py,sha256=Forib096qbWud11aKw9TRbBN1bIQjOmE3ZYY62vhVYk,9831
63
67
  app/modules/proxy/schemas.py,sha256=55pXtUCl2R_93kAPOJJ7Ji4Jn3qVu10vq2KSCCkNdp4,2748
64
- app/modules/proxy/service.py,sha256=U0mAC90HDbovOMd1CmyaZ0AvAYNRsxq8jDjR4nbdd64,18932
68
+ app/modules/proxy/service.py,sha256=wXXXu4Nd88RDzY1EQvA7OZPH5EW1F9hCINB1vGjT1q8,23647
69
+ app/modules/proxy/sticky_repository.py,sha256=peFaAnaCVr064W-Sh0Kjvz-MuPbBfrh86RLQOGJ6qqs,2177
65
70
  app/modules/proxy/types.py,sha256=iqEyoO8vGr8N5oEzUSvVWCai7UZbJAU62IvO7JNS9qs,927
66
71
  app/modules/request_logs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
72
  app/modules/request_logs/api.py,sha256=6nV2Uv_hnK7WI3gNpKrgTx4MyUQIXk1QxKp40nij0Xo,1037
68
- app/modules/request_logs/repository.py,sha256=SnwDwD84wgslmdIgYFGD8ReUYmIyhxT6MOjFAolLBC8,3333
69
- app/modules/request_logs/schemas.py,sha256=GSCi4TEWMmQ-THsQl2irRrA_msU8jzsqKSBDFn7hiJU,592
70
- app/modules/request_logs/service.py,sha256=1G1-z6ytP1d51aLQxSE4nrtCC0y5Ys1aX5XtmzccbJw,2477
73
+ app/modules/request_logs/repository.py,sha256=iIaXGIG1qjol4UiTsVASIJN0g5zIdMg6tRNolkXZ0pI,3460
74
+ app/modules/request_logs/schemas.py,sha256=Xlpk2hEVCQxA2TI7dh4EA6G0kSfzYcT_LlvyTVLIW2w,675
75
+ app/modules/request_logs/service.py,sha256=8IvC4GIhPh6FVQGkuww1Ej1onFanObnX7mY3tDEy99A,2643
76
+ app/modules/settings/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
77
+ app/modules/settings/api.py,sha256=dbnvKUF4lgX5ipxOik4SJBeJ6hctJKe3lcT2Uf0F5BQ,1461
78
+ app/modules/settings/repository.py,sha256=r-GjuURPcftCx5HnFOZKFpv34g9bvy0m53owVCc3tpI,1204
79
+ app/modules/settings/schemas.py,sha256=V27alhIa4LtJHNPuZIj_EGX0v0kzn0v3esn27s1z_bc,343
80
+ app/modules/settings/service.py,sha256=FUayQsKEsZf2roe84j4HRH7vTXXi4W071IZlOCOkjpM,1188
71
81
  app/modules/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
- app/modules/shared/schemas.py,sha256=SSoWTiSeiSxD06MDn9NbZWhx12JMQtvjJMIk1Kvxwio,240
82
+ app/modules/shared/schemas.py,sha256=2JgPY4N6vRXX7C0ETackykmjKgTjobZYBMWiwpPfLic,650
73
83
  app/modules/usage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
74
84
  app/modules/usage/api.py,sha256=jpPc2VSjmiP6DjRZvotHCh_trLINOngSyTdS7MY9fgg,1108
75
85
  app/modules/usage/repository.py,sha256=DtJI4kgajW7YUJ0JKJjdNCPBXT_fdBwqDoepi9aznyA,4791
76
- app/modules/usage/schemas.py,sha256=kh0D2IbIFkl5WTo8XPs-7AC4C8jRpyX0pF6KYQH9ifU,1579
77
- app/modules/usage/service.py,sha256=n0kdL_AMT271jWim-BK2jiI7I1H1D9d8uzbHtw13xSg,9853
78
- app/modules/usage/updater.py,sha256=TevRdwO0vA3HF-E-UkDMPs_3Bv-8qu_8VZX1Jd0AcfE,5591
79
- app/static/7.css,sha256=9EHW2Ouff2fRXgcQbYuCuglxTFgQZGNLLTXKTS6S5aI,80687
80
- app/static/index.css,sha256=ct1FfBg0PY7c6KxSS6A7JM_WDwdeJqhQa4pXTgyJ19Q,8786
81
- app/static/index.html,sha256=FmWQ0l40rTBsHIR9i0ttGTzG17ePASsO2VDv6Q48CNw,24477
82
- app/static/index.js,sha256=a45sCr7nXJE9muacaxRNTao7l_j8rpHKuEck_qvgSaI,52644
83
- codex_lb-0.2.0.dist-info/METADATA,sha256=3tQgTWLJl4waxyKXczt119GY9JNS3ICqtvJ1vnNMrfk,3828
84
- codex_lb-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
85
- codex_lb-0.2.0.dist-info/entry_points.txt,sha256=SEa5T6Uz2Fhy574No6Y0XyGmYi3PXLrhu2xStJTqyI8,42
86
- codex_lb-0.2.0.dist-info/licenses/LICENSE,sha256=cHPibxiL0TXwrUX_kNY6ym544EX1UCzKhxdaca5cFuk,1062
87
- codex_lb-0.2.0.dist-info/RECORD,,
86
+ app/modules/usage/schemas.py,sha256=eCgunQOvQeYEqv9IecjunONPVLpg2MPn_YGzsnBTcpQ,1633
87
+ app/modules/usage/service.py,sha256=8-XX8m4TgQteum-a53l0DS-EL2NnC4r9artehfFltvM,10315
88
+ app/modules/usage/updater.py,sha256=VAeRFvgfpZLCTGxfikefw35ecs8Ja7EoWldIldXDjmE,6783
89
+ app/static/7.css,sha256=NjFS937GS7Wg-LTSon5eoiZaHo9fAIbHZ3ikkEDg4Qw,82378
90
+ app/static/index.css,sha256=amajcY4psagBwwg5FqgINHNebEHdL1IPRmBGG4fcm24,9271
91
+ app/static/index.html,sha256=JPEj3IsVKsK2ub6-_hxKj_Yr9OnRuobHsX8plH9SzDE,26860
92
+ app/static/index.js,sha256=p_BZxgROGRLXxy9JGAB03SUARDwHesbmUy0LAVq-Z7w,59013
93
+ codex_lb-0.3.0.dist-info/METADATA,sha256=RuWBdjeptPK-OigdjI-mbDVVcj2QvTRUU7s1RbwajKU,3828
94
+ codex_lb-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
95
+ codex_lb-0.3.0.dist-info/entry_points.txt,sha256=SEa5T6Uz2Fhy574No6Y0XyGmYi3PXLrhu2xStJTqyI8,42
96
+ codex_lb-0.3.0.dist-info/licenses/LICENSE,sha256=cHPibxiL0TXwrUX_kNY6ym544EX1UCzKhxdaca5cFuk,1062
97
+ codex_lb-0.3.0.dist-info/RECORD,,