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.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <head>
5
5
  <meta charset="utf-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
- <meta name="color-scheme" content="light">
7
+ <meta name="color-scheme" content="light dark">
8
8
  <title>Codex LB</title>
9
9
  <link rel="icon"
10
10
  href="data:image/svg+xml,%3Csvg xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;32&quot; height=&quot;32&quot; fill=&quot;none&quot; viewBox=&quot;0 0 32 32&quot;%3E%3Cpath stroke=&quot;%23000&quot; stroke-linecap=&quot;round&quot; stroke-width=&quot;2.484&quot; d=&quot;M22.356 19.797H17.17M9.662 12.29l1.979 3.576a.511.511 0 0 1-.005.504l-1.974 3.409M30.758 16c0 8.15-6.607 14.758-14.758 14.758-8.15 0-14.758-6.607-14.758-14.758C1.242 7.85 7.85 1.242 16 1.242c8.15 0 14.758 6.608 14.758 14.758Z&quot;/%3E%3C/svg%3E"
@@ -44,7 +44,7 @@
44
44
  <div class="loading-overlay" x-show="isLoading" x-transition.opacity>
45
45
  <div class="loading-panel" role="status" aria-live="polite">
46
46
  <span class="spinner animate" aria-hidden="true"></span>
47
- <div>Loading dashboard...</div>
47
+ <div>Loading Dashboard...</div>
48
48
  </div>
49
49
  </div>
50
50
  <section class="tabs" aria-label="Codex FE tabs">
@@ -149,6 +149,161 @@
149
149
 
150
150
  <div class="panel" style="margin-top: 12px">
151
151
  <h3>Recent requests</h3>
152
+
153
+ <div class="controls-toolbar">
154
+ <div class="controls-group">
155
+ <input type="text" class="filter-input" placeholder="Search..." x-model="filtersDraft.search"
156
+ @keyup.enter="applyFilters()" style="width: 200px;">
157
+
158
+ <div class="single-select" x-data="{ open: false }" @click.outside="open = false">
159
+ <button type="button" class="filter-select single-select-trigger" @click="open = !open"
160
+ :aria-expanded="open">
161
+ <span x-text="timeframeLabel(filtersDraft.timeframe)"></span>
162
+ </button>
163
+ <div class="single-select-menu" x-show="open" x-transition.opacity>
164
+ <label class="single-select-item" :class="{ 'is-selected': filtersDraft.timeframe === 'all' }">
165
+ <input type="radio" name="timeframe" value="all" x-model="filtersDraft.timeframe"
166
+ @change="open = false">
167
+ <span>All time</span>
168
+ </label>
169
+ <label class="single-select-item" :class="{ 'is-selected': filtersDraft.timeframe === '1h' }">
170
+ <input type="radio" name="timeframe" value="1h" x-model="filtersDraft.timeframe"
171
+ @change="open = false">
172
+ <span>Last 1h</span>
173
+ </label>
174
+ <label class="single-select-item" :class="{ 'is-selected': filtersDraft.timeframe === '24h' }">
175
+ <input type="radio" name="timeframe" value="24h" x-model="filtersDraft.timeframe"
176
+ @change="open = false">
177
+ <span>Last 24h</span>
178
+ </label>
179
+ <label class="single-select-item" :class="{ 'is-selected': filtersDraft.timeframe === '7d' }">
180
+ <input type="radio" name="timeframe" value="7d" x-model="filtersDraft.timeframe"
181
+ @change="open = false">
182
+ <span>Last 7d</span>
183
+ </label>
184
+ </div>
185
+ </div>
186
+
187
+
188
+ <div class="multi-select" x-data="{ open: false }" @click.outside="open = false">
189
+ <button type="button" class="filter-select multi-select-trigger" @click="open = !open"
190
+ :aria-expanded="open" :disabled="requestLogOptions.isLoading">
191
+ <span
192
+ x-text="multiSelectSummary(filtersDraft.accountIds, 'All accounts', 'account', 'accounts')"></span>
193
+ </button>
194
+ <div class="multi-select-menu" x-show="open" x-transition.opacity>
195
+ <div class="multi-select-scroller">
196
+ <div class="multi-select-actions">
197
+ <button type="button" class="multi-select-action"
198
+ @click="filtersDraft.accountIds = []; open = false">Clear</button>
199
+ </div>
200
+ <template x-for="accountId in requestLogOptions.accountIds" :key="accountId">
201
+ <label class="multi-select-item">
202
+ <input type="checkbox" :checked="filtersDraft.accountIds.includes(accountId)"
203
+ @change="toggleMultiSelectValue('accountIds', accountId)">
204
+ <span class="multi-select-label" x-text="accountFilterLabel(accountId)"></span>
205
+ </label>
206
+ </template>
207
+ </div>
208
+ </div>
209
+ </div>
210
+
211
+ <div class="multi-select" x-data="{ open: false }" @click.outside="open = false">
212
+ <button type="button" class="filter-select multi-select-trigger" @click="open = !open"
213
+ :aria-expanded="open" :disabled="requestLogOptions.isLoading">
214
+ <span
215
+ x-text="multiSelectSummary(filtersDraft.modelOptions, 'All models', 'model', 'models')"></span>
216
+ </button>
217
+ <div class="multi-select-menu" x-show="open" x-transition.opacity>
218
+ <div class="multi-select-scroller">
219
+ <div class="multi-select-actions">
220
+ <button type="button" class="multi-select-action"
221
+ @click="filtersDraft.modelOptions = []; open = false">Clear</button>
222
+ </div>
223
+ <template x-for="modelOption in requestLogOptions.modelOptions"
224
+ :key="modelOptionValue(modelOption)">
225
+ <label class="multi-select-item">
226
+ <input type="checkbox"
227
+ :checked="filtersDraft.modelOptions.includes(modelOptionValue(modelOption))"
228
+ @change="toggleMultiSelectValue('modelOptions', modelOptionValue(modelOption))">
229
+ <span class="multi-select-label" x-text="modelOptionLabel(modelOption)"></span>
230
+ </label>
231
+ </template>
232
+ </div>
233
+ </div>
234
+ </div>
235
+
236
+ <div class="multi-select" x-data="{ open: false }" @click.outside="open = false">
237
+ <button type="button" class="filter-select multi-select-trigger" @click="open = !open"
238
+ :aria-expanded="open">
239
+ <span x-text="multiSelectSummary(filtersDraft.statuses, 'All status', 'status', 'statuses')"></span>
240
+ </button>
241
+ <div class="multi-select-menu" x-show="open" x-transition.opacity>
242
+ <div class="multi-select-scroller">
243
+ <div class="multi-select-actions">
244
+ <button type="button" class="multi-select-action"
245
+ @click="filtersDraft.statuses = []; open = false">Clear</button>
246
+ </div>
247
+ <label class="multi-select-item">
248
+ <input type="checkbox" :checked="filtersDraft.statuses.includes('ok')"
249
+ @change="toggleMultiSelectValue('statuses', 'ok')">
250
+ <span class="multi-select-label">OK</span>
251
+ </label>
252
+ <label class="multi-select-item">
253
+ <input type="checkbox" :checked="filtersDraft.statuses.includes('rate_limit')"
254
+ @change="toggleMultiSelectValue('statuses', 'rate_limit')">
255
+ <span class="multi-select-label">Rate Limit</span>
256
+ </label>
257
+ <label class="multi-select-item">
258
+ <input type="checkbox" :checked="filtersDraft.statuses.includes('quota')"
259
+ @change="toggleMultiSelectValue('statuses', 'quota')">
260
+ <span class="multi-select-label">Quota</span>
261
+ </label>
262
+ <label class="multi-select-item">
263
+ <input type="checkbox" :checked="filtersDraft.statuses.includes('error')"
264
+ @change="toggleMultiSelectValue('statuses', 'error')">
265
+ <span class="multi-select-label">Error</span>
266
+ </label>
267
+ </div>
268
+ </div>
269
+ </div>
270
+
271
+ <input type="number" class="filter-input" placeholder="Min Cost ($)" x-model="filtersDraft.minCost"
272
+ @keyup.enter="applyFilters()" style="width: 110px;" step="0.01">
273
+
274
+ <button type="button" class="filter-apply" @click="applyFilters()"
275
+ :disabled="recentRequestsState.isLoading">Apply</button>
276
+ </div>
277
+
278
+ <div class="controls-group">
279
+ <span style="font-size: 13px; color: var(--text-muted);">Show:</span>
280
+ <div class="single-select" x-data="{ open: false }" @click.outside="open = false">
281
+ <button type="button" class="filter-select single-select-trigger" @click="open = !open"
282
+ :aria-expanded="open" style="min-width: 60px">
283
+ <span x-text="pagination.limit"></span>
284
+ </button>
285
+ <div class="single-select-menu" x-show="open" x-transition.opacity style="min-width: 80px">
286
+ <label class="single-select-item" :class="{ 'is-selected': pagination.limit == 25 }">
287
+ <input type="radio" name="limit" value="25" x-model="pagination.limit" @change="open = false">
288
+ <span>25</span>
289
+ </label>
290
+ <label class="single-select-item" :class="{ 'is-selected': pagination.limit == 50 }">
291
+ <input type="radio" name="limit" value="50" x-model="pagination.limit" @change="open = false">
292
+ <span>50</span>
293
+ </label>
294
+ <label class="single-select-item" :class="{ 'is-selected': pagination.limit == 100 }">
295
+ <input type="radio" name="limit" value="100" x-model="pagination.limit" @change="open = false">
296
+ <span>100</span>
297
+ </label>
298
+ <label class="single-select-item" :class="{ 'is-selected': pagination.limit == 250 }">
299
+ <input type="radio" name="limit" value="250" x-model="pagination.limit" @change="open = false">
300
+ <span>250</span>
301
+ </label>
302
+ </div>
303
+ </div>
304
+ </div>
305
+ </div>
306
+
152
307
  <div class="table-wrap table-wrap--requests table-wrap--column-lines has-scrollbar">
153
308
  <table>
154
309
  <thead>
@@ -158,21 +313,41 @@
158
313
  <th style="width: 15%">Model</th>
159
314
  <th style="width: 10%">Status</th>
160
315
  <th style="width: 15%">Error</th>
161
- <th style="text-align: right; width: 10%">Tokens</th>
162
- <th style="text-align: right; width: 10%">Cost</th>
316
+ <th style="width: 10%">Tokens</th>
317
+ <th style="width: 10%">Cost</th>
163
318
  </tr>
164
319
  </thead>
165
320
  <tbody>
166
321
  <template x-for="request in dashboard.requests" :key="request.key">
167
322
  <tr>
168
- <td x-text="request.time"></td>
323
+ <td>
324
+ <div x-text="request.time.time"></div>
325
+ <div x-text="request.time.date" class="text-muted" style="font-size: 11px;"></div>
326
+ </td>
169
327
  <td class="cell-truncate" x-text="request.account" :title="request.account"></td>
170
328
  <td class="cell-truncate" x-text="request.model" :title="request.model"></td>
171
329
  <td><span class="status-pill" :class="request.status.class" x-text="request.status.label"></span>
172
330
  </td>
173
- <td class="cell-truncate" x-text="request.error" :title="request.errorTitle || ''"></td>
174
- <td style="text-align: right" x-text="request.tokens"></td>
175
- <td style="text-align: right" x-text="request.cost"></td>
331
+ <td class="cell-error">
332
+ <div class="error-cell" :class="request.isErrorPlaceholder ? 'placeholder' : ''"
333
+ x-data="{ expanded: false }">
334
+ <div class="error-text" :class="!expanded ? 'truncated' : ''"
335
+ x-text="expanded ? request.errorTitle : request.error"
336
+ :title="!expanded ? request.errorTitle : ''"></div>
337
+ <template x-if="request.isTruncated">
338
+ <button class="error-toggle" @click="expanded = !expanded"
339
+ x-text="expanded ? 'Less' : 'More'"></button>
340
+ </template>
341
+ </div>
342
+ </td>
343
+ <td>
344
+ <div x-text="request.tokens.total"></div>
345
+ <template x-if="request.tokens.cached">
346
+ <div x-text="`${request.tokens.cached} Cached`" class="text-muted" style="font-size: 11px;">
347
+ </div>
348
+ </template>
349
+ </td>
350
+ <td x-text="request.cost"></td>
176
351
  </tr>
177
352
  </template>
178
353
  </tbody>