codex-lb 0.3.1__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.
Files changed (37) hide show
  1. app/core/clients/proxy.py +33 -3
  2. app/core/config/settings.py +9 -8
  3. app/core/handlers/__init__.py +3 -0
  4. app/core/handlers/exceptions.py +39 -0
  5. app/core/middleware/__init__.py +9 -0
  6. app/core/middleware/api_errors.py +33 -0
  7. app/core/middleware/request_decompression.py +101 -0
  8. app/core/middleware/request_id.py +27 -0
  9. app/core/openai/chat_requests.py +172 -0
  10. app/core/openai/chat_responses.py +534 -0
  11. app/core/openai/message_coercion.py +60 -0
  12. app/core/openai/models_catalog.py +72 -0
  13. app/core/openai/requests.py +23 -5
  14. app/core/openai/v1_requests.py +92 -0
  15. app/db/models.py +3 -3
  16. app/db/session.py +25 -8
  17. app/dependencies.py +43 -16
  18. app/main.py +13 -67
  19. app/modules/accounts/repository.py +25 -10
  20. app/modules/proxy/api.py +94 -0
  21. app/modules/proxy/load_balancer.py +75 -58
  22. app/modules/proxy/repo_bundle.py +23 -0
  23. app/modules/proxy/service.py +127 -102
  24. app/modules/request_logs/api.py +61 -7
  25. app/modules/request_logs/repository.py +131 -16
  26. app/modules/request_logs/schemas.py +11 -2
  27. app/modules/request_logs/service.py +97 -20
  28. app/modules/usage/service.py +65 -4
  29. app/modules/usage/updater.py +58 -26
  30. app/static/index.css +378 -1
  31. app/static/index.html +183 -8
  32. app/static/index.js +308 -13
  33. {codex_lb-0.3.1.dist-info → codex_lb-0.5.0.dist-info}/METADATA +42 -3
  34. {codex_lb-0.3.1.dist-info → codex_lb-0.5.0.dist-info}/RECORD +37 -25
  35. {codex_lb-0.3.1.dist-info → codex_lb-0.5.0.dist-info}/WHEEL +0 -0
  36. {codex_lb-0.3.1.dist-info → codex_lb-0.5.0.dist-info}/entry_points.txt +0 -0
  37. {codex_lb-0.3.1.dist-info → codex_lb-0.5.0.dist-info}/licenses/LICENSE +0 -0
app/static/index.js CHANGED
@@ -12,7 +12,8 @@
12
12
  usageSummary: "/api/usage/summary",
13
13
  usageWindow: (window) =>
14
14
  `/api/usage/window?window=${encodeURIComponent(window)}`,
15
- requestLogs: (limit) => `/api/request-logs?limit=${limit}`,
15
+ requestLogs: "/api/request-logs",
16
+ requestLogOptions: "/api/request-logs/options",
16
17
  oauthStart: "/api/oauth/start",
17
18
  oauthStatus: "/api/oauth/status",
18
19
  oauthComplete: "/api/oauth/complete",
@@ -79,9 +80,20 @@
79
80
  ok: "active",
80
81
  rate_limit: "limited",
81
82
  quota: "exceeded",
82
- error: "deactivated",
83
+ error: "error",
83
84
  };
84
85
 
86
+ const MODEL_OPTION_DELIMITER = ":::";
87
+
88
+ const createDefaultRequestFilters = () => ({
89
+ search: "",
90
+ timeframe: "all",
91
+ accountIds: [],
92
+ modelOptions: [],
93
+ statuses: [],
94
+ minCost: "",
95
+ });
96
+
85
97
  const KNOWN_PLAN_TYPES = new Set([
86
98
  "free",
87
99
  "plus",
@@ -180,11 +192,16 @@
180
192
  minimumFractionDigits: 2,
181
193
  maximumFractionDigits: 2,
182
194
  });
183
- const timeLongFormatter = new Intl.DateTimeFormat("en-US", {
195
+ const timeFormatter = new Intl.DateTimeFormat("en-US", {
184
196
  hour: "2-digit",
185
197
  minute: "2-digit",
186
198
  second: "2-digit",
187
199
  });
200
+ const dateFormatter = new Intl.DateTimeFormat("en-US", {
201
+ year: "numeric",
202
+ month: "2-digit",
203
+ day: "2-digit",
204
+ });
188
205
 
189
206
  const createEmptyDashboardData = () => ({
190
207
  lastSyncAt: "",
@@ -373,9 +390,12 @@
373
390
  const formatTimeLong = (iso) => {
374
391
  const date = parseDate(iso);
375
392
  if (!date) {
376
- return "--";
393
+ return { time: "--", date: "--" };
377
394
  }
378
- return timeLongFormatter.format(date);
395
+ return {
396
+ time: timeFormatter.format(date),
397
+ date: dateFormatter.format(date),
398
+ };
379
399
  };
380
400
 
381
401
  const formatRelative = (ms) => {
@@ -768,7 +788,7 @@
768
788
  typeof windowMinutes === "number" && windowMinutes > 0
769
789
  ? windowMinutes / 60
770
790
  : 24 * 7;
771
- return Math.round(numeric / hours);
791
+ return numeric / hours;
772
792
  };
773
793
 
774
794
  const countByStatus = (accounts) =>
@@ -1055,6 +1075,9 @@
1055
1075
  const rawError = request.errorMessage || request.errorCode || "";
1056
1076
  const accountLabel = formatAccountLabel(request.accountId, accounts);
1057
1077
  const modelLabel = formatModelLabel(request.model, request.reasoningEffort);
1078
+ const totalTokens = formatCompactNumber(request.tokens);
1079
+ const cachedInputTokens = toNumber(request.cachedInputTokens);
1080
+ const cachedTokens = cachedInputTokens > 0 ? formatCompactNumber(cachedInputTokens) : null;
1058
1081
  return {
1059
1082
  key: `${request.requestId}-${request.timestamp}`,
1060
1083
  requestId: request.requestId,
@@ -1065,10 +1088,16 @@
1065
1088
  class: requestStatusClass(request.status),
1066
1089
  label: requestStatusLabel(request.status),
1067
1090
  },
1068
- tokens: formatTokensWithCached(request.tokens, request.cachedInputTokens),
1091
+ tokens: {
1092
+ total: totalTokens,
1093
+ cached: cachedTokens,
1094
+ },
1095
+ tokensTooltip: formatTokensWithCached(request.tokens, request.cachedInputTokens),
1069
1096
  cost: formatCurrency(request.cost),
1070
1097
  error: rawError ? truncateText(rawError, 80) : "--",
1071
1098
  errorTitle: rawError,
1099
+ isTruncated: rawError.length > 20,
1100
+ isErrorPlaceholder: !rawError,
1072
1101
  };
1073
1102
  });
1074
1103
 
@@ -1237,8 +1266,107 @@
1237
1266
  const fetchUsageWindow = async (window) =>
1238
1267
  fetchJson(API_ENDPOINTS.usageWindow(window), `usage window (${window})`);
1239
1268
 
1240
- const fetchRequestLogs = async (limit) =>
1241
- fetchJson(API_ENDPOINTS.requestLogs(limit), "request logs");
1269
+ const parseMinCostUsd = (value) => {
1270
+ if (value === null || value === undefined) {
1271
+ return null;
1272
+ }
1273
+ const asString = String(value).trim();
1274
+ if (!asString) {
1275
+ return null;
1276
+ }
1277
+ const numeric = Number(asString);
1278
+ return Number.isFinite(numeric) ? numeric : null;
1279
+ };
1280
+
1281
+ const buildSinceIsoFromTimeframe = (timeframe) => {
1282
+ const now = Date.now();
1283
+ if (timeframe === "1h") {
1284
+ return new Date(now - 60 * 60 * 1000).toISOString();
1285
+ }
1286
+ if (timeframe === "24h") {
1287
+ return new Date(now - 24 * 60 * 60 * 1000).toISOString();
1288
+ }
1289
+ if (timeframe === "7d") {
1290
+ return new Date(now - 7 * 24 * 60 * 60 * 1000).toISOString();
1291
+ }
1292
+ return null;
1293
+ };
1294
+
1295
+ const buildRequestLogsQueryParams = ({ filters, pagination }) => {
1296
+ const params = {
1297
+ limit: pagination?.limit,
1298
+ offset: pagination?.offset,
1299
+ };
1300
+ if (filters?.search) {
1301
+ params.search = filters.search;
1302
+ }
1303
+ if (Array.isArray(filters?.accountIds) && filters.accountIds.length) {
1304
+ params.accountId = filters.accountIds.filter(Boolean);
1305
+ }
1306
+ if (Array.isArray(filters?.modelOptions) && filters.modelOptions.length) {
1307
+ params.modelOption = filters.modelOptions.filter(Boolean);
1308
+ }
1309
+ if (Array.isArray(filters?.statuses) && filters.statuses.length) {
1310
+ params.status = filters.statuses.filter(Boolean);
1311
+ }
1312
+ const since = buildSinceIsoFromTimeframe(filters?.timeframe);
1313
+ if (since) {
1314
+ params.since = since;
1315
+ }
1316
+ return params;
1317
+ };
1318
+
1319
+ const applyClientSideRequestFilters = (requests, filters) => {
1320
+ const minCostUsd = parseMinCostUsd(filters?.minCost);
1321
+ if (minCostUsd === null) {
1322
+ return requests;
1323
+ }
1324
+ return (requests || []).filter((entry) => (entry.cost || 0) >= minCostUsd);
1325
+ };
1326
+
1327
+ const buildQueryString = (params) => {
1328
+ const searchParams = new URLSearchParams();
1329
+ if (!params || typeof params !== "object") {
1330
+ return "";
1331
+ }
1332
+ for (const [key, value] of Object.entries(params)) {
1333
+ if (value === null || value === undefined) {
1334
+ continue;
1335
+ }
1336
+ if (Array.isArray(value)) {
1337
+ const filtered = value
1338
+ .map((item) => String(item ?? "").trim())
1339
+ .filter(Boolean);
1340
+ for (const item of filtered) {
1341
+ searchParams.append(key, item);
1342
+ }
1343
+ continue;
1344
+ }
1345
+ if (typeof value === "string" && value.trim() === "") {
1346
+ continue;
1347
+ }
1348
+ searchParams.append(key, String(value));
1349
+ }
1350
+ return searchParams.toString();
1351
+ };
1352
+
1353
+ const fetchRequestLogs = async (params) => {
1354
+ const query = buildQueryString(params);
1355
+ return fetchJson(
1356
+ query ? `${API_ENDPOINTS.requestLogs}?${query}` : API_ENDPOINTS.requestLogs,
1357
+ "request logs",
1358
+ );
1359
+ };
1360
+
1361
+ const fetchRequestLogOptions = async (params) => {
1362
+ const query = buildQueryString(params);
1363
+ return fetchJson(
1364
+ query
1365
+ ? `${API_ENDPOINTS.requestLogOptions}?${query}`
1366
+ : API_ENDPOINTS.requestLogOptions,
1367
+ "request log options",
1368
+ );
1369
+ };
1242
1370
 
1243
1371
  const normalizeSettingsPayload = (payload) => ({
1244
1372
  stickyThreadsEnabled: Boolean(payload?.stickyThreadsEnabled),
@@ -1258,6 +1386,24 @@
1258
1386
  ui: createUiConfig(),
1259
1387
  dashboardData: createEmptyDashboardData(),
1260
1388
  dashboard: createEmptyDashboardView(),
1389
+
1390
+ filtersDraft: createDefaultRequestFilters(),
1391
+ filtersApplied: createDefaultRequestFilters(),
1392
+ pagination: {
1393
+ limit: 25,
1394
+ offset: 0,
1395
+ },
1396
+ recentRequestsState: {
1397
+ isLoading: false,
1398
+ error: "",
1399
+ },
1400
+ requestLogOptions: {
1401
+ accountIds: [],
1402
+ modelOptions: [],
1403
+ isLoading: false,
1404
+ error: "",
1405
+ },
1406
+
1261
1407
  settings: {
1262
1408
  stickyThreadsEnabled: false,
1263
1409
  preferEarlierResetAccounts: false,
@@ -1305,12 +1451,19 @@
1305
1451
  isLoading: true,
1306
1452
  hasInitialized: false,
1307
1453
  refreshPromise: null,
1454
+
1308
1455
  async init() {
1309
1456
  if (this.hasInitialized) {
1310
1457
  return;
1311
1458
  }
1312
1459
  this.hasInitialized = true;
1313
1460
  this.view = getViewFromPath(window.location.pathname);
1461
+
1462
+ this.$watch("pagination.limit", () => {
1463
+ this.pagination.offset = 0;
1464
+ this.refreshRequests();
1465
+ });
1466
+
1314
1467
  await this.loadData();
1315
1468
  this.syncTitle();
1316
1469
  this.syncUrl(true);
@@ -1329,6 +1482,115 @@
1329
1482
  }
1330
1483
  });
1331
1484
  },
1485
+
1486
+ async refreshRequests() {
1487
+ try {
1488
+ this.recentRequestsState.isLoading = true;
1489
+ this.recentRequestsState.error = "";
1490
+ const params = buildRequestLogsQueryParams({
1491
+ filters: this.filtersApplied,
1492
+ pagination: this.pagination,
1493
+ });
1494
+ const requestsData = await fetchRequestLogs(params);
1495
+ const normalized = normalizeRequestLogsPayload(requestsData);
1496
+ const requests = applyClientSideRequestFilters(
1497
+ normalized,
1498
+ this.filtersApplied,
1499
+ );
1500
+
1501
+ this.dashboardData.recentRequests = requests;
1502
+
1503
+ this.dashboard = buildDashboardView(this);
1504
+ } catch (err) {
1505
+ this.recentRequestsState.error =
1506
+ err?.message || "Failed to refresh requests.";
1507
+ console.error("Failed to refresh requests:", err);
1508
+ } finally {
1509
+ this.recentRequestsState.isLoading = false;
1510
+ }
1511
+ },
1512
+
1513
+ applyFilters() {
1514
+ this.pagination.offset = 0;
1515
+ const accountIds = Array.isArray(this.filtersDraft.accountIds)
1516
+ ? [...new Set(this.filtersDraft.accountIds.map(String).filter(Boolean))]
1517
+ : [];
1518
+ const modelOptions = Array.isArray(this.filtersDraft.modelOptions)
1519
+ ? [...new Set(this.filtersDraft.modelOptions.map(String).filter(Boolean))]
1520
+ : [];
1521
+ const statuses = Array.isArray(this.filtersDraft.statuses)
1522
+ ? [...new Set(this.filtersDraft.statuses.map(String).filter(Boolean))]
1523
+ : [];
1524
+ this.filtersApplied = {
1525
+ ...createDefaultRequestFilters(),
1526
+ ...this.filtersDraft,
1527
+ accountIds,
1528
+ modelOptions,
1529
+ statuses,
1530
+ };
1531
+ this.refreshRequests();
1532
+ },
1533
+
1534
+ resetFilters() {
1535
+ this.filtersDraft = createDefaultRequestFilters();
1536
+ this.applyFilters();
1537
+ },
1538
+
1539
+ accountFilterLabel(accountId) {
1540
+ return formatAccountLabel(accountId, this.accounts.rows);
1541
+ },
1542
+
1543
+ modelOptionValue(option) {
1544
+ const model = String(option?.model || "").trim();
1545
+ const effort = String(option?.reasoningEffort || "").trim();
1546
+ return `${model}${MODEL_OPTION_DELIMITER}${effort}`;
1547
+ },
1548
+
1549
+ modelOptionLabel(option) {
1550
+ return formatModelLabel(option?.model, option?.reasoningEffort);
1551
+ },
1552
+
1553
+ toggleMultiSelectValue(listKey, rawValue) {
1554
+ const value = String(rawValue ?? "").trim();
1555
+ if (!value) {
1556
+ return;
1557
+ }
1558
+ const current = Array.isArray(this.filtersDraft[listKey])
1559
+ ? [...this.filtersDraft[listKey]]
1560
+ : [];
1561
+ const index = current.indexOf(value);
1562
+ if (index >= 0) {
1563
+ current.splice(index, 1);
1564
+ } else {
1565
+ current.push(value);
1566
+ }
1567
+ this.filtersDraft = { ...this.filtersDraft, [listKey]: current };
1568
+ },
1569
+
1570
+ multiSelectSummary(values, emptyLabel, singularLabel, pluralLabel) {
1571
+ const list = Array.isArray(values)
1572
+ ? values.map((value) => String(value ?? "").trim()).filter(Boolean)
1573
+ : [];
1574
+ if (list.length === 0) {
1575
+ return emptyLabel;
1576
+ }
1577
+ if (list.length === 1) {
1578
+ return `1 ${singularLabel}`;
1579
+ }
1580
+ const plural = pluralLabel ? String(pluralLabel) : `${singularLabel}s`;
1581
+ return `${list.length} ${plural}`;
1582
+ },
1583
+
1584
+ timeframeLabel(value) {
1585
+ const labels = {
1586
+ all: "All time",
1587
+ "1h": "Last 1h",
1588
+ "24h": "Last 24h",
1589
+ "7d": "Last 7d",
1590
+ };
1591
+ return labels[value] || "All time";
1592
+ },
1593
+
1332
1594
  async loadData() {
1333
1595
  try {
1334
1596
  await this.refreshAll({ silent: true });
@@ -1351,22 +1613,32 @@
1351
1613
  return this.refreshPromise;
1352
1614
  }
1353
1615
  const { preferredId, silent = false } = options;
1616
+ this.requestLogOptions.isLoading = true;
1617
+ this.requestLogOptions.error = "";
1354
1618
  this.refreshPromise = (async () => {
1619
+ const params = buildRequestLogsQueryParams({
1620
+ filters: this.filtersApplied,
1621
+ pagination: this.pagination,
1622
+ });
1623
+
1355
1624
  const [
1356
1625
  accountsResult,
1357
1626
  summaryResult,
1358
1627
  primaryResult,
1359
1628
  secondaryResult,
1360
1629
  requestLogsResult,
1630
+ requestLogOptionsResult,
1361
1631
  settingsResult,
1362
1632
  ] = await Promise.allSettled([
1363
1633
  fetchAccounts(),
1364
1634
  fetchUsageSummary(),
1365
1635
  fetchUsageWindow("primary"),
1366
1636
  fetchUsageWindow("secondary"),
1367
- fetchRequestLogs(50),
1637
+ fetchRequestLogs(params),
1638
+ fetchRequestLogOptions({}),
1368
1639
  fetchSettings(),
1369
1640
  ]);
1641
+
1370
1642
  const errors = [];
1371
1643
  if (accountsResult.status !== "fulfilled") {
1372
1644
  throw accountsResult.reason;
@@ -1398,6 +1670,21 @@
1398
1670
  errors.push(requestLogsResult.reason);
1399
1671
  }
1400
1672
 
1673
+ if (requestLogOptionsResult.status === "fulfilled") {
1674
+ const payload = requestLogOptionsResult.value;
1675
+ this.requestLogOptions.accountIds = Array.isArray(payload?.accountIds)
1676
+ ? payload.accountIds
1677
+ : [];
1678
+ this.requestLogOptions.modelOptions = Array.isArray(payload?.modelOptions)
1679
+ ? payload.modelOptions
1680
+ : [];
1681
+ } else {
1682
+ this.requestLogOptions.error =
1683
+ requestLogOptionsResult.reason?.message ||
1684
+ "Failed to load request log options.";
1685
+ errors.push(requestLogOptionsResult.reason);
1686
+ }
1687
+
1401
1688
  const settings =
1402
1689
  settingsResult.status === "fulfilled" ? settingsResult.value : null;
1403
1690
  if (settingsResult.status === "rejected") {
@@ -1415,7 +1702,10 @@
1415
1702
  summary,
1416
1703
  primaryUsage,
1417
1704
  secondaryUsage,
1418
- requestLogs,
1705
+ requestLogs: applyClientSideRequestFilters(
1706
+ requestLogs,
1707
+ this.filtersApplied,
1708
+ ),
1419
1709
  settings,
1420
1710
  },
1421
1711
  preferredId,
@@ -1437,6 +1727,7 @@
1437
1727
  await this.refreshPromise;
1438
1728
  } finally {
1439
1729
  this.refreshPromise = null;
1730
+ this.requestLogOptions.isLoading = false;
1440
1731
  }
1441
1732
  },
1442
1733
  applyData(data, preferredId) {
@@ -1960,15 +2251,19 @@
1960
2251
  },
1961
2252
  get statusItems() {
1962
2253
  const lastSync = formatTimeLong(this.dashboardData.lastSyncAt);
2254
+ const lastSyncLabel =
2255
+ lastSync && lastSync.time && lastSync.time !== "--"
2256
+ ? `${lastSync.time} · ${lastSync.date}`
2257
+ : "--";
1963
2258
  const items =
1964
2259
  this.view === "accounts"
1965
2260
  ? [
1966
2261
  `Selection: ${this.accounts.selectedId || "--"}`,
1967
2262
  `Rotation: ${this.dashboardData.routing?.rotationEnabled ? "enabled" : "disabled"}`,
1968
- `Last sync: ${lastSync}`,
2263
+ `Last sync: ${lastSyncLabel}`,
1969
2264
  ]
1970
2265
  : [
1971
- `Last sync: ${lastSync}`,
2266
+ `Last sync: ${lastSyncLabel}`,
1972
2267
  `Routing: ${routingLabel(this.dashboardData.routing?.strategy)}`,
1973
2268
  `Backend: ${this.backendPath}`,
1974
2269
  ];
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codex-lb
3
- Version: 0.3.1
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,12 +49,25 @@ 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
 
55
+ <!--
56
+ About
57
+ Codex/ChatGPT account load balancer & proxy with usage tracking, dashboard, and OpenCode-compatible endpoints
58
+
59
+ Topics
60
+ python oauth sqlalchemy dashboard load-balancer openai rate-limit api-proxy codex fastapi usage-tracking chatgpt opencode
61
+
62
+ Resources
63
+ -->
64
+
54
65
  # codex-lb
55
66
 
56
67
  Load balancer for ChatGPT accounts. Pool multiple accounts, track usage, view everything in a dashboard.
57
68
 
69
+ ## Screenshots
70
+
58
71
  ### Main Dashboard View
59
72
 
60
73
  ![main dashboard view](docs/screenshots/dashboard.jpg)
@@ -82,8 +95,6 @@ uvx codex-lb
82
95
 
83
96
  Open [localhost:2455](http://localhost:2455) → Add account → Done.
84
97
 
85
-
86
-
87
98
  ## Codex CLI & Extension Setup
88
99
 
89
100
  Add to `~/.codex/config.toml`:
@@ -101,6 +112,32 @@ chatgpt_base_url = "http://127.0.0.1:2455"
101
112
  requires_openai_auth = true # Required: enables model selection in Codex IDE extension
102
113
  ```
103
114
 
115
+ ## OpenCode Setup
116
+
117
+ Run:
118
+
119
+ ```bash
120
+ opencode auth login
121
+ ```
122
+
123
+ Then select `OpenAI` -> `Manually enter API Key` and enter any value.
124
+
125
+ Add the following to `~/.config/opencode/opencode.json`:
126
+
127
+ ```jsonc
128
+ {
129
+ ...
130
+ "provider": {
131
+ "openai": {
132
+ "options": {
133
+ "baseURL": "http://127.0.0.1:2455/v1"
134
+ }
135
+ },
136
+ ...
137
+ }
138
+ }
139
+ ```
140
+
104
141
  ## Data
105
142
 
106
143
  All data stored in `~/.codex-lb/`:
@@ -122,6 +159,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
122
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>
123
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>
124
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>
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>
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>
125
164
  </tr>
126
165
  </tbody>
127
166
  </table>
@@ -1,7 +1,7 @@
1
1
  app/__init__.py,sha256=uqZSnn_VEL8TIUxsYqdf4zA2ByJYfjA06fArVAzrHFo,89
2
2
  app/cli.py,sha256=gkIAkYOT9SbQjUDnVmwhVKZeKjL3YJCMrOjFINwBx54,544
3
- app/dependencies.py,sha256=kfB_TxeZve_cnBxhOHZezKwkOwTNFwoHzNCJDUkGJD8,4377
4
- app/main.py,sha256=wCELolhaBfJCEfFfSELNTQ5hZ1Xo-G5Wa3vMkIOY6gk,4665
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
@@ -16,14 +16,25 @@ app/core/balancer/types.py,sha256=gDgjlTy-NH3YhHYl2-YYpIabnckN9Q8-4cRy6S1u0K4,19
16
16
  app/core/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  app/core/clients/http.py,sha256=yfFwsaIZNbSmNtyV02WnMKn00JdoNNSlSIx5xB3QY4o,987
18
18
  app/core/clients/oauth.py,sha256=XgAzQVAMudBUCQ9nGKUC9N7zagSiawBdhIVmxf9HHwQ,11832
19
- app/core/clients/proxy.py,sha256=ZWs0TBYcOgDz_jZ3tucDsRiqQ_9TGxU1oPGiI1mu-bE,9378
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=h6JoHfsaqZpUmv8RPKw38zv45eHyjv53PbHCw3pK7h8,2648
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=Nnd4ZtxUtXP-jJ7LGKpPf_JDBIFo-X9k0qMw9SyANTc,1675
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
27
38
  app/core/usage/__init__.py,sha256=8SBpiClJR2wl653Cj1DTvmVUvi2jzU6kKXJtq91ofmE,5726
28
39
  app/core/usage/logs.py,sha256=8TrE9nccEjIJslMfAk8AJLmX_Zzeksu8qMTvrGwePx0,2129
29
40
  app/core/usage/models.py,sha256=FtBQx4Rb7jpwcqxmGXxg7RTVV17LK1QOSWaJIkaaNoQ,878
@@ -36,8 +47,8 @@ app/core/utils/retry.py,sha256=UmBap1Wh-CBT7r4fHzVb_PI9-LR9-HjUtDzRnhRjP2U,822
36
47
  app/core/utils/sse.py,sha256=DJMOU4vW5Ir_4WeL5t5t7i33aRMnqVPU0eQvGn4sBv8,537
37
48
  app/core/utils/time.py,sha256=B6FfSe43Eq_puE6eourly1X3gajyihK2VOAwJ8M3wyI,497
38
49
  app/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
- app/db/models.py,sha256=Wp7OUMYtqkT-8BD-aoYUsdZgTCMMXg-e0VU1vSKMRg8,5244
40
- app/db/session.py,sha256=T_UdGcqzn5YXo-fuRW8s5xJQvOuJsV4eTSZE0_V2C44,3910
50
+ app/db/models.py,sha256=vjQ2C3l8J1zlsRz9gVzM4fbcX43NGrj51cfCsM4EjNk,5304
51
+ app/db/session.py,sha256=U51SC4oDIHgoiW0pc5upCjvKyL896j3aYkyfH3EmJ58,4578
41
52
  app/db/migrations/__init__.py,sha256=hRZVLgAQfP-okuhIiKkQtwfnPtBvYXcLJTqg0Yex1Vc,2633
42
53
  app/db/migrations/versions/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
43
54
  app/db/migrations/versions/add_accounts_chatgpt_account_id.py,sha256=5QHfko2f7dq0G34Cqe8CkcKbQor2W5LegfnVu_HgZN8,978
@@ -49,7 +60,7 @@ app/modules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
60
  app/modules/accounts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
61
  app/modules/accounts/api.py,sha256=rSkYrg3_p_JE1kHZ4xXa5lBFTdT40v1oNAmlTDGOguY,2807
51
62
  app/modules/accounts/auth_manager.py,sha256=YUKdeHbszRLYZ4uRQzvk6ICk2cB73LjlJMEiWphBWWw,4682
52
- app/modules/accounts/repository.py,sha256=xOj47_BeYzVPj_WBejrlGtlTDd0tNR4o6sZIrM8QriE,3382
63
+ app/modules/accounts/repository.py,sha256=ZhDQ5kjesB4MrJjQ_ZQ8n8HexQUMCuw5RJ0w6y6uhfc,4144
53
64
  app/modules/accounts/schemas.py,sha256=gtlbPg5uxM3t_V5JxCL6eP-UaU6TSE0UoX2yIpxM_a0,1659
54
65
  app/modules/accounts/service.py,sha256=M3_SRD21yp0YN820IHdqNIlZsynNsSUN6TO6RcESDeI,8973
55
66
  app/modules/health/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -61,18 +72,19 @@ app/modules/oauth/schemas.py,sha256=sdDKP7u9bO87lcZXjK7uSokurHPS22nN2p_jkw9iEBc,
61
72
  app/modules/oauth/service.py,sha256=fEzUtoq1g-20NgWZlB_maA2XB-scMa0HJV24SxZPnqQ,12908
62
73
  app/modules/oauth/templates/oauth_success.html,sha256=YNSGUIozcZEJQjpFtM2sgF4n8jqfbmx8LRwdXTraym4,3799
63
74
  app/modules/proxy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
64
- app/modules/proxy/api.py,sha256=BR_qNlNZg2Ft5GYQ-7AzlLlJFf6RVJmlzJzFr_KHUIc,2921
75
+ app/modules/proxy/api.py,sha256=quBEQzF32tnsbk72tEVqx8NrqwlTYkqZthH4oe-ndBs,6174
65
76
  app/modules/proxy/helpers.py,sha256=-XVTNTpkDvJ3KfsuGHztfUVBGBFm2H-AaNTNH2dNe6o,8739
66
- app/modules/proxy/load_balancer.py,sha256=Forib096qbWud11aKw9TRbBN1bIQjOmE3ZYY62vhVYk,9831
77
+ app/modules/proxy/load_balancer.py,sha256=6Tbeba1zaYEfCctCC5VMUgDF0jXK2ICzeiFqEhYYQSU,10618
78
+ app/modules/proxy/repo_bundle.py,sha256=yP4ggYpHNvKTfHD-YOqQ0oL2PlR7qFwmmasWMO155LI,776
67
79
  app/modules/proxy/schemas.py,sha256=55pXtUCl2R_93kAPOJJ7Ji4Jn3qVu10vq2KSCCkNdp4,2748
68
- app/modules/proxy/service.py,sha256=wXXXu4Nd88RDzY1EQvA7OZPH5EW1F9hCINB1vGjT1q8,23647
80
+ app/modules/proxy/service.py,sha256=-hlz26DMuEqv6kH8g_5kfnS8yTI-lT-jHq0oWnPydiY,24741
69
81
  app/modules/proxy/sticky_repository.py,sha256=peFaAnaCVr064W-Sh0Kjvz-MuPbBfrh86RLQOGJ6qqs,2177
70
82
  app/modules/proxy/types.py,sha256=iqEyoO8vGr8N5oEzUSvVWCai7UZbJAU62IvO7JNS9qs,927
71
83
  app/modules/request_logs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
- app/modules/request_logs/api.py,sha256=6nV2Uv_hnK7WI3gNpKrgTx4MyUQIXk1QxKp40nij0Xo,1037
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
84
+ app/modules/request_logs/api.py,sha256=NkLHo8Yuy4S6m00yUIOmzcag9scEaSG2QL7CDRYfyUE,3158
85
+ app/modules/request_logs/repository.py,sha256=ufX1NhOh8geZ8iFgMRhJY0SG-3r4v8sJHlybdms0tYo,8663
86
+ app/modules/request_logs/schemas.py,sha256=MFS_MW0mW3cGyIulxJN81dJ3SS2bt0tFE67-DzYX1Pg,944
87
+ app/modules/request_logs/service.py,sha256=ruZz5QAKYSrJbYpImZzU2va1sLFfamWL8dOhqU-GG0A,5485
76
88
  app/modules/settings/__init__.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35
77
89
  app/modules/settings/api.py,sha256=dbnvKUF4lgX5ipxOik4SJBeJ6hctJKe3lcT2Uf0F5BQ,1461
78
90
  app/modules/settings/repository.py,sha256=r-GjuURPcftCx5HnFOZKFpv34g9bvy0m53owVCc3tpI,1204
@@ -84,13 +96,13 @@ app/modules/usage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
84
96
  app/modules/usage/api.py,sha256=jpPc2VSjmiP6DjRZvotHCh_trLINOngSyTdS7MY9fgg,1108
85
97
  app/modules/usage/repository.py,sha256=DtJI4kgajW7YUJ0JKJjdNCPBXT_fdBwqDoepi9aznyA,4791
86
98
  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/index.css,sha256=k75jd3sqISoTjl-cqyrMo72EUlwr65FQEX9SEFSAaRA,23223
90
- app/static/index.html,sha256=K79S0wMIla6i3oXFwVpSiG2jNlE8x5XxKzsxu177yyo,29131
91
- app/static/index.js,sha256=odwrhEiSlwq3WW7WcnO4-AGVh_N-TnD5AMuzagOtg4M,61544
92
- codex_lb-0.3.1.dist-info/METADATA,sha256=KyjxuKYrjCvIXQYcDQ1M5kASLBR6Vu6u3RplrzT6Znk,5840
93
- codex_lb-0.3.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
94
- codex_lb-0.3.1.dist-info/entry_points.txt,sha256=SEa5T6Uz2Fhy574No6Y0XyGmYi3PXLrhu2xStJTqyI8,42
95
- codex_lb-0.3.1.dist-info/licenses/LICENSE,sha256=cHPibxiL0TXwrUX_kNY6ym544EX1UCzKhxdaca5cFuk,1062
96
- codex_lb-0.3.1.dist-info/RECORD,,
99
+ app/modules/usage/service.py,sha256=8nEP6fcd67ZYuaVXIlcGloA2DKzzUMsxVSRZSNkJOeo,12381
100
+ app/modules/usage/updater.py,sha256=TUFLIjIe8c0m06bnO6iumzAyFuMED3B1ry3Ty5o8JRo,7821
101
+ app/static/index.css,sha256=4EcLWTHAkhbqldi9fE1Y_bQCtmJuXUgEs0JHV-KmV9w,31757
102
+ app/static/index.html,sha256=g1U2AlbvGChev5KIyPoVI5TJ3dnvEOqoAot1tENvQOs,39799
103
+ app/static/index.js,sha256=q-IXWQURYksd-i68yXwyqBazs-AYiz3Tvk5t_B6NbSY,69750
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,,