v2.6.0: Usage Dashboard, per-provider tracking, OAuth file picker
- Usage Dashboard: visual cards with success rate bars, token stats, latency - Per-model breakdown and error tracking per provider - Proxy records usage-stats.json after every request - Google OAuth: browse for client_secret.json instead of fixed path - Auto-copies selected file to ~/.cache/codex-proxy/
This commit is contained in:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,5 +1,19 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v2.6.0 (2026-05-20)
|
||||||
|
|
||||||
|
- **Usage Dashboard** — per-provider tracking with visual cards
|
||||||
|
- Request counts, success/failure rates, token usage, latency stats
|
||||||
|
- Color-coded success rate bars (green/yellow/red)
|
||||||
|
- Per-model breakdown showing request counts
|
||||||
|
- Last error and last used timestamp
|
||||||
|
- Sorted by most-used provider
|
||||||
|
- Refresh button for live updates
|
||||||
|
- **Proxy usage tracking** — records every request to `usage-stats.json`
|
||||||
|
- **Google OAuth**: browse for `client_secret.json` with file picker dialog
|
||||||
|
- No longer requires copying to a specific path manually
|
||||||
|
- Auto-copies selected file to `~/.cache/codex-proxy/`
|
||||||
|
|
||||||
## v2.5.1 (2026-05-20)
|
## v2.5.1 (2026-05-20)
|
||||||
|
|
||||||
- **Adaptive retry for transient errors** (429/502/503)
|
- **Adaptive retry for transient errors** (429/502/503)
|
||||||
|
|||||||
Binary file not shown.
BIN
codex-launcher_2.6.0_all.deb
Normal file
BIN
codex-launcher_2.6.0_all.deb
Normal file
Binary file not shown.
@@ -25,6 +25,11 @@ model_catalog_json = ""
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
CHANGELOG = [
|
CHANGELOG = [
|
||||||
|
("2.6.0", "2026-05-20", [
|
||||||
|
"Usage Dashboard — per-provider request/token/latency tracking",
|
||||||
|
"Visual cards with success rate bars, model breakdown, error tracking",
|
||||||
|
"Google OAuth: browse for client_secret.json instead of fixed path",
|
||||||
|
]),
|
||||||
("2.5.1", "2026-05-20", [
|
("2.5.1", "2026-05-20", [
|
||||||
"Adaptive retry for 429/502/503 errors with exponential backoff",
|
"Adaptive retry for 429/502/503 errors with exponential backoff",
|
||||||
"BGP routes also retry transient errors before failing over",
|
"BGP routes also retry transient errors before failing over",
|
||||||
@@ -635,12 +640,15 @@ class LauncherWin(Gtk.Window):
|
|||||||
# header row
|
# header row
|
||||||
hdr = Gtk.Box(spacing=8)
|
hdr = Gtk.Box(spacing=8)
|
||||||
vbox.pack_start(hdr, False, False, 0)
|
vbox.pack_start(hdr, False, False, 0)
|
||||||
lbl = Gtk.Label(label="<b>Codex Launcher v2.5.1</b>")
|
lbl = Gtk.Label(label="<b>Codex Launcher v2.6.0</b>")
|
||||||
lbl.set_use_markup(True)
|
lbl.set_use_markup(True)
|
||||||
hdr.pack_start(lbl, False, False, 0)
|
hdr.pack_start(lbl, False, False, 0)
|
||||||
changelog_btn = Gtk.Button(label="Changelog")
|
changelog_btn = Gtk.Button(label="Changelog")
|
||||||
changelog_btn.connect("clicked", lambda b: self._show_changelog())
|
changelog_btn.connect("clicked", lambda b: self._show_changelog())
|
||||||
hdr.pack_end(changelog_btn, False, False, 0)
|
hdr.pack_end(changelog_btn, False, False, 0)
|
||||||
|
usage_btn = Gtk.Button(label="Usage")
|
||||||
|
usage_btn.connect("clicked", lambda b: self._open_usage())
|
||||||
|
hdr.pack_end(usage_btn, False, False, 0)
|
||||||
bgp_btn = Gtk.Button(label="AI BGP")
|
bgp_btn = Gtk.Button(label="AI BGP")
|
||||||
bgp_btn.connect("clicked", lambda b: self._open_bgp())
|
bgp_btn.connect("clicked", lambda b: self._open_bgp())
|
||||||
hdr.pack_end(bgp_btn, False, False, 0)
|
hdr.pack_end(bgp_btn, False, False, 0)
|
||||||
@@ -942,6 +950,15 @@ class LauncherWin(Gtk.Window):
|
|||||||
d = Gtk.MessageDialog(self, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, f"Error: {e}")
|
d = Gtk.MessageDialog(self, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, f"Error: {e}")
|
||||||
d.run(); d.destroy()
|
d.run(); d.destroy()
|
||||||
|
|
||||||
|
def _open_usage(self):
|
||||||
|
try:
|
||||||
|
self._usage_window = UsageWindow(self)
|
||||||
|
self._usage_window.connect("destroy", lambda *_: setattr(self, "_usage_window", None))
|
||||||
|
except Exception as e:
|
||||||
|
import traceback; traceback.print_exc()
|
||||||
|
d = Gtk.MessageDialog(self, 0, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, f"Error: {e}")
|
||||||
|
d.run(); d.destroy()
|
||||||
|
|
||||||
def _backup_profile(self):
|
def _backup_profile(self):
|
||||||
chooser = Gtk.FileChooserDialog(
|
chooser = Gtk.FileChooserDialog(
|
||||||
title="Backup Codex Profile",
|
title="Backup Codex Profile",
|
||||||
@@ -1807,12 +1824,15 @@ class EditEndpointDialog(Gtk.Dialog):
|
|||||||
|
|
||||||
def _google_oauth_flow(self):
|
def _google_oauth_flow(self):
|
||||||
token_path = os.path.expanduser("~/.cache/codex-proxy/google-oauth-token.json")
|
token_path = os.path.expanduser("~/.cache/codex-proxy/google-oauth-token.json")
|
||||||
client_secret_path = os.path.expanduser("~/.cache/codex-proxy/client_secret.json")
|
default_cs_path = os.path.expanduser("~/.cache/codex-proxy/client_secret.json")
|
||||||
|
client_secret_path = None
|
||||||
|
|
||||||
if not os.path.exists(client_secret_path):
|
if os.path.exists(default_cs_path):
|
||||||
|
client_secret_path = default_cs_path
|
||||||
|
else:
|
||||||
dlg = Gtk.Dialog(title="Google OAuth Setup", parent=self, modal=True)
|
dlg = Gtk.Dialog(title="Google OAuth Setup", parent=self, modal=True)
|
||||||
dlg.add_button("Open Google Console", 1)
|
dlg.add_button("Open Google Console", 1)
|
||||||
dlg.add_button("I have client_secret.json", 2)
|
dlg.add_button("Browse for client_secret.json", 2)
|
||||||
dlg.add_button("Cancel", Gtk.ResponseType.CANCEL)
|
dlg.add_button("Cancel", Gtk.ResponseType.CANCEL)
|
||||||
dlg.set_default_size(500, 320)
|
dlg.set_default_size(500, 320)
|
||||||
area = dlg.get_content_area()
|
area = dlg.get_content_area()
|
||||||
@@ -1828,9 +1848,9 @@ class EditEndpointDialog(Gtk.Dialog):
|
|||||||
"2. APIs & Services → Credentials\n"
|
"2. APIs & Services → Credentials\n"
|
||||||
" → Create OAuth 2.0 Client ID (Desktop app)\n"
|
" → Create OAuth 2.0 Client ID (Desktop app)\n"
|
||||||
"3. Add redirect URI: http://localhost:8085\n"
|
"3. Add redirect URI: http://localhost:8085\n"
|
||||||
"4. Download client_secret.json\n"
|
"4. Download client_secret.json\n\n"
|
||||||
f"5. Save to: {client_secret_path}\n\n"
|
"Then click 'Browse for client_secret.json'\n"
|
||||||
"Then click 'I have client_secret.json'"
|
f"It will be auto-copied to: {default_cs_path}"
|
||||||
), xalign=0)
|
), xalign=0)
|
||||||
area.pack_start(steps, False, False, 4)
|
area.pack_start(steps, False, False, 4)
|
||||||
area.show_all()
|
area.show_all()
|
||||||
@@ -1838,7 +1858,32 @@ class EditEndpointDialog(Gtk.Dialog):
|
|||||||
dlg.destroy()
|
dlg.destroy()
|
||||||
if r == 1:
|
if r == 1:
|
||||||
subprocess.Popen(["xdg-open", "https://console.cloud.google.com/apis/credentials"])
|
subprocess.Popen(["xdg-open", "https://console.cloud.google.com/apis/credentials"])
|
||||||
return
|
return
|
||||||
|
if r == 2:
|
||||||
|
chooser = Gtk.FileChooserDialog(
|
||||||
|
title="Select client_secret.json",
|
||||||
|
parent=self,
|
||||||
|
action=Gtk.FileChooserAction.OPEN,
|
||||||
|
)
|
||||||
|
chooser.add_button("Cancel", Gtk.ResponseType.CANCEL)
|
||||||
|
chooser.add_button("Open", Gtk.ResponseType.OK)
|
||||||
|
filt = Gtk.FileFilter()
|
||||||
|
filt.set_name("JSON files")
|
||||||
|
filt.add_pattern("*.json")
|
||||||
|
chooser.add_filter(filt)
|
||||||
|
chooser.set_current_folder(os.path.expanduser("~/Downloads"))
|
||||||
|
if chooser.run() == Gtk.ResponseType.OK:
|
||||||
|
src = chooser.get_filename()
|
||||||
|
chooser.destroy()
|
||||||
|
if src and os.path.exists(src):
|
||||||
|
import shutil as _shutil
|
||||||
|
os.makedirs(os.path.dirname(default_cs_path), exist_ok=True)
|
||||||
|
_shutil.copy2(src, default_cs_path)
|
||||||
|
client_secret_path = default_cs_path
|
||||||
|
else:
|
||||||
|
chooser.destroy()
|
||||||
|
if not client_secret_path:
|
||||||
|
return
|
||||||
|
|
||||||
with open(client_secret_path) as f:
|
with open(client_secret_path) as f:
|
||||||
cs = json.load(f)
|
cs = json.load(f)
|
||||||
@@ -2465,6 +2510,197 @@ class BGPRouteDialog(Gtk.Dialog):
|
|||||||
self._combo_model.set_active(0)
|
self._combo_model.set_active(0)
|
||||||
|
|
||||||
|
|
||||||
|
_USAGE_COLORS = {
|
||||||
|
"green": "#27ae60", "yellow": "#f39c12", "orange": "#e67e22",
|
||||||
|
"red": "#e74c3c", "blue": "#3498db", "purple": "#9b59b6",
|
||||||
|
"dark": "#2c3e50", "light": "#ecf0f1", "mid": "#bdc3c7",
|
||||||
|
}
|
||||||
|
|
||||||
|
_USAGE_STATS_FILE = HOME / ".cache/codex-proxy/usage-stats.json"
|
||||||
|
|
||||||
|
def _load_usage_stats():
|
||||||
|
try:
|
||||||
|
if _USAGE_STATS_FILE.exists():
|
||||||
|
return json.loads(_USAGE_STATS_FILE.read_text())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return {"providers": {}, "updated": None}
|
||||||
|
|
||||||
|
def _bar_color(pct):
|
||||||
|
if pct < 0.5:
|
||||||
|
return _USAGE_COLORS["green"]
|
||||||
|
if pct < 0.8:
|
||||||
|
return _USAGE_COLORS["yellow"]
|
||||||
|
return _USAGE_COLORS["red"]
|
||||||
|
|
||||||
|
class UsageWindow(Gtk.Window):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__(title="Usage Stats")
|
||||||
|
self.set_transient_for(parent)
|
||||||
|
self.set_default_size(640, 560)
|
||||||
|
self.set_position(Gtk.WindowPosition.CENTER)
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
||||||
|
self.add(vbox)
|
||||||
|
|
||||||
|
header = Gtk.Box(spacing=8)
|
||||||
|
header.set_margin_start(16)
|
||||||
|
header.set_margin_end(16)
|
||||||
|
header.set_margin_top(12)
|
||||||
|
header.set_margin_bottom(8)
|
||||||
|
vbox.pack_start(header, False, False, 0)
|
||||||
|
title = Gtk.Label()
|
||||||
|
title.set_markup('<span font="14" weight="bold" foreground="#2c3e50">Usage Dashboard</span>')
|
||||||
|
header.pack_start(title, False, False, 0)
|
||||||
|
refresh_btn = Gtk.Button(label="Refresh")
|
||||||
|
refresh_btn.connect("clicked", lambda b: self._refresh())
|
||||||
|
header.pack_end(refresh_btn, False, False, 0)
|
||||||
|
self._updated_lbl = Gtk.Label()
|
||||||
|
self._updated_lbl.set_markup('<span foreground="#95a5a6" size="small">Never</span>')
|
||||||
|
header.pack_end(self._updated_lbl, False, False, 8)
|
||||||
|
|
||||||
|
sep = Gtk.Separator()
|
||||||
|
vbox.pack_start(sep, False, False, 0)
|
||||||
|
|
||||||
|
self._cards_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
|
||||||
|
sw = Gtk.ScrolledWindow()
|
||||||
|
sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
|
||||||
|
sw.add(self._cards_box)
|
||||||
|
vbox.pack_start(sw, True, True, 0)
|
||||||
|
|
||||||
|
self._refresh()
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
def _refresh(self):
|
||||||
|
for c in self._cards_box.get_children():
|
||||||
|
self._cards_box.remove(c)
|
||||||
|
stats = _load_usage_stats()
|
||||||
|
updated = stats.get("updated")
|
||||||
|
if updated:
|
||||||
|
self._updated_lbl.set_markup(f'<span foreground="#95a5a6" size="small">Updated: {updated}</span>')
|
||||||
|
providers = stats.get("providers", {})
|
||||||
|
if not providers:
|
||||||
|
empty = Gtk.Label()
|
||||||
|
empty.set_markup('<span foreground="#95a5a6" size="large">No usage data yet.\nLaunch a session to start tracking.</span>')
|
||||||
|
empty.set_margin_top(60)
|
||||||
|
self._cards_box.pack_start(empty, False, False, 0)
|
||||||
|
self._cards_box.show_all()
|
||||||
|
return
|
||||||
|
|
||||||
|
sorted_providers = sorted(providers.items(), key=lambda x: x[1].get("total_requests", 0), reverse=True)
|
||||||
|
for prov_name, prov_data in sorted_providers:
|
||||||
|
card = self._build_card(prov_name, prov_data)
|
||||||
|
self._cards_box.pack_start(card, False, False, 0)
|
||||||
|
self._cards_box.show_all()
|
||||||
|
|
||||||
|
def _build_card(self, name, data):
|
||||||
|
frame = Gtk.Frame()
|
||||||
|
frame.set_margin_start(12)
|
||||||
|
frame.set_margin_end(12)
|
||||||
|
frame.set_margin_top(4)
|
||||||
|
frame.set_margin_bottom(4)
|
||||||
|
style = frame.get_style_context()
|
||||||
|
style.add_class("card")
|
||||||
|
|
||||||
|
outer = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
|
||||||
|
outer.set_margin_start(12)
|
||||||
|
outer.set_margin_end(12)
|
||||||
|
outer.set_margin_top(8)
|
||||||
|
outer.set_margin_bottom(8)
|
||||||
|
frame.add(outer)
|
||||||
|
|
||||||
|
top_row = Gtk.Box(spacing=8)
|
||||||
|
outer.pack_start(top_row, False, False, 0)
|
||||||
|
|
||||||
|
total = data.get("total_requests", 0)
|
||||||
|
ok = data.get("successes", 0)
|
||||||
|
fail = data.get("failures", 0)
|
||||||
|
success_rate = ok / total if total > 0 else 1.0
|
||||||
|
|
||||||
|
name_lbl = Gtk.Label()
|
||||||
|
short = name.replace("https://", "").replace("http://", "").split("/")[0]
|
||||||
|
name_lbl.set_markup(f'<span weight="bold" foreground="#2c3e50" size="medium">{short}</span>')
|
||||||
|
top_row.pack_start(name_lbl, False, False, 0)
|
||||||
|
|
||||||
|
req_lbl = Gtk.Label()
|
||||||
|
req_lbl.set_markup(f'<span foreground="#7f8c8d" size="small">{total} requests</span>')
|
||||||
|
top_row.pack_start(req_lbl, False, False, 8)
|
||||||
|
|
||||||
|
if fail > 0:
|
||||||
|
err_lbl = Gtk.Label()
|
||||||
|
err_lbl.set_markup(f'<span foreground="{_USAGE_COLORS["red"]}" size="small">{fail} failed</span>')
|
||||||
|
top_row.pack_start(err_lbl, False, False, 4)
|
||||||
|
|
||||||
|
last_used = data.get("last_used", "")
|
||||||
|
if last_used:
|
||||||
|
lu_lbl = Gtk.Label()
|
||||||
|
lu_lbl.set_markup(f'<span foreground="#95a5a6" size="x-small">{last_used}</span>')
|
||||||
|
top_row.pack_end(lu_lbl, False, False, 0)
|
||||||
|
|
||||||
|
# Progress bar for success rate
|
||||||
|
bar = Gtk.ProgressBar()
|
||||||
|
bar.set_fraction(success_rate)
|
||||||
|
bar_pct = int(success_rate * 100)
|
||||||
|
bar.set_text(f"{bar_pct}% success")
|
||||||
|
bar.set_show_text(True)
|
||||||
|
bar.set_margin_top(2)
|
||||||
|
bar.set_margin_bottom(2)
|
||||||
|
color = _bar_color(1.0 - success_rate)
|
||||||
|
bar_css = f'progress {{ background-color: {color}; border-radius: 4px; }} trough {{ border-radius: 4px; min-height: 10px; }}'
|
||||||
|
provider = Gtk.CssProvider()
|
||||||
|
provider.load_from_data(bar_css.encode())
|
||||||
|
bar.get_style_context().add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||||
|
outer.pack_start(bar, False, False, 0)
|
||||||
|
|
||||||
|
# Stats row
|
||||||
|
stats_row = Gtk.Box(spacing=16)
|
||||||
|
outer.pack_start(stats_row, False, False, 0)
|
||||||
|
|
||||||
|
t_in = data.get("total_tokens_in", 0)
|
||||||
|
t_out = data.get("total_tokens_out", 0)
|
||||||
|
dur = data.get("total_duration_s", 0.0)
|
||||||
|
avg_dur = dur / total if total > 0 else 0
|
||||||
|
|
||||||
|
for label, value in [
|
||||||
|
("Tokens In", f"{t_in:,}"),
|
||||||
|
("Tokens Out", f"{t_out:,}"),
|
||||||
|
("Avg Latency", f"{avg_dur:.1f}s"),
|
||||||
|
]:
|
||||||
|
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1)
|
||||||
|
l = Gtk.Label()
|
||||||
|
l.set_markup(f'<span foreground="#95a5a6" size="x-small">{label}</span>')
|
||||||
|
box.pack_start(l, False, False, 0)
|
||||||
|
v = Gtk.Label()
|
||||||
|
v.set_markup(f'<span weight="bold" foreground="#2c3e50" size="small">{value}</span>')
|
||||||
|
box.pack_start(v, False, False, 0)
|
||||||
|
stats_row.pack_start(box, False, False, 0)
|
||||||
|
|
||||||
|
# Models breakdown
|
||||||
|
models = data.get("models", {})
|
||||||
|
if len(models) > 0:
|
||||||
|
model_str = " ".join(
|
||||||
|
f'<span foreground="#3498db" size="x-small">{m}</span> '
|
||||||
|
f'<span foreground="#7f8c8d" size="x-small">({md.get("requests",0)})</span>'
|
||||||
|
for m, md in sorted(models.items(), key=lambda x: x[1].get("requests", 0), reverse=True)[:4]
|
||||||
|
)
|
||||||
|
m_lbl = Gtk.Label()
|
||||||
|
m_lbl.set_markup(f'<span size="x-small">Models:</span> {model_str}')
|
||||||
|
m_lbl.set_line_wrap(True)
|
||||||
|
m_lbl.set_xalign(0)
|
||||||
|
outer.pack_start(m_lbl, False, False, 2)
|
||||||
|
|
||||||
|
# Error info
|
||||||
|
last_err = data.get("last_error")
|
||||||
|
if last_err:
|
||||||
|
err_lbl = Gtk.Label()
|
||||||
|
err_lbl.set_markup(f'<span foreground="{_USAGE_COLORS["red"]}" size="x-small">Last error: {last_err}</span>')
|
||||||
|
err_lbl.set_xalign(0)
|
||||||
|
outer.pack_start(err_lbl, False, False, 0)
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
for d in [LOG_DIR, PROXY_CONFIG_DIR]:
|
for d in [LOG_DIR, PROXY_CONFIG_DIR]:
|
||||||
d.mkdir(parents=True, exist_ok=True)
|
d.mkdir(parents=True, exist_ok=True)
|
||||||
|
|||||||
@@ -141,6 +141,43 @@ _pool = uuid.uuid4().hex[:8]
|
|||||||
_response_store = {}
|
_response_store = {}
|
||||||
_MAX_STORED = 50
|
_MAX_STORED = 50
|
||||||
|
|
||||||
|
_stats_path = os.path.join(_LOG_DIR, "usage-stats.json")
|
||||||
|
_stats_lock = threading.Lock()
|
||||||
|
|
||||||
|
def _load_stats():
|
||||||
|
try:
|
||||||
|
if os.path.exists(_stats_path):
|
||||||
|
return json.load(open(_stats_path))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return {"providers": {}, "updated": None}
|
||||||
|
|
||||||
|
def _record_usage(provider, model, success, duration_s, tokens_in=0, tokens_out=0, error_type=None):
|
||||||
|
with _stats_lock:
|
||||||
|
stats = _load_stats()
|
||||||
|
p = stats["providers"].setdefault(provider, {
|
||||||
|
"total_requests": 0, "successes": 0, "failures": 0,
|
||||||
|
"total_tokens_in": 0, "total_tokens_out": 0,
|
||||||
|
"total_duration_s": 0.0, "models": {}, "last_used": None, "last_error": None,
|
||||||
|
})
|
||||||
|
p["total_requests"] += 1
|
||||||
|
p["total_tokens_in"] += tokens_in
|
||||||
|
p["total_tokens_out"] += tokens_out
|
||||||
|
p["total_duration_s"] += duration_s
|
||||||
|
p["last_used"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||||
|
if success:
|
||||||
|
p["successes"] += 1
|
||||||
|
else:
|
||||||
|
p["failures"] += 1
|
||||||
|
p["last_error"] = error_type or "unknown"
|
||||||
|
m = p["models"].setdefault(model, {"requests": 0, "tokens_in": 0, "tokens_out": 0})
|
||||||
|
m["requests"] += 1
|
||||||
|
m["tokens_in"] += tokens_in
|
||||||
|
m["tokens_out"] += tokens_out
|
||||||
|
stats["updated"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||||
|
with open(_stats_path, "w") as f:
|
||||||
|
json.dump(stats, f, indent=2)
|
||||||
|
|
||||||
def store_response(resp_id, input_data, output_items):
|
def store_response(resp_id, input_data, output_items):
|
||||||
if not resp_id:
|
if not resp_id:
|
||||||
return
|
return
|
||||||
@@ -1161,6 +1198,10 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
|
|
||||||
def _forward_oa_compat(self, upstream, stream, model, chat_body, body, input_data, fwd, target):
|
def _forward_oa_compat(self, upstream, stream, model, chat_body, body, input_data, fwd, target):
|
||||||
n_items = len(input_data) if isinstance(input_data, list) else 1
|
n_items = len(input_data) if isinstance(input_data, list) else 1
|
||||||
|
t0 = time.time()
|
||||||
|
provider = TARGET_URL.split("//")[-1].split("/")[0]
|
||||||
|
if BGP_ROUTES:
|
||||||
|
provider = "bgp:" + (BGP_ROUTES[0].get("name", "pool") if BGP_ROUTES else "unknown")
|
||||||
|
|
||||||
if stream:
|
if stream:
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
@@ -1205,6 +1246,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
_log_resp(last_resp_id, last_status, last_output)
|
_log_resp(last_resp_id, last_status, last_output)
|
||||||
if last_resp_id and input_data is not None:
|
if last_resp_id and input_data is not None:
|
||||||
store_response(last_resp_id, input_data, last_output)
|
store_response(last_resp_id, input_data, last_output)
|
||||||
|
_record_usage(provider, model, success, time.time() - t0, error_type="length" if not success else None)
|
||||||
|
|
||||||
# Auto-retry on finish_reason=length with no content
|
# Auto-retry on finish_reason=length with no content
|
||||||
if finish_reason == "length" and not has_content and isinstance(input_data, list) and len(input_data) > 5:
|
if finish_reason == "length" and not has_content and isinstance(input_data, list) and len(input_data) > 5:
|
||||||
@@ -1234,6 +1276,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|||||||
_log_resp(rid, result.get("status"), result.get("output", []))
|
_log_resp(rid, result.get("status"), result.get("output", []))
|
||||||
if rid and input_data is not None:
|
if rid and input_data is not None:
|
||||||
store_response(rid, input_data, result.get("output", []))
|
store_response(rid, input_data, result.get("output", []))
|
||||||
|
_record_usage(provider, model, success, time.time() - t0)
|
||||||
|
|
||||||
def _forward_oa_compat_retry(self, req, model, chat_body, body, input_data):
|
def _forward_oa_compat_retry(self, req, model, chat_body, body, input_data):
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user