From cf8ebd2064b4c8e7e2b96dd6fe3bc8cab410f22d Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 20 May 2026 18:11:39 +0400 Subject: [PATCH] v2.7.0: Usage Dashboard redesign (OpenUsage-inspired), TCP_NODELAY streaming, Anthropic prompt caching --- CHANGELOG.md | 25 +++ codex-launcher_2.6.1_all.deb | Bin 34158 -> 0 bytes codex-launcher_2.7.0_all.deb | Bin 0 -> 35098 bytes src/codex-launcher-gui | 421 +++++++++++++++++++++++++++-------- src/translate-proxy.py | 30 ++- 5 files changed, 374 insertions(+), 102 deletions(-) delete mode 100644 codex-launcher_2.6.1_all.deb create mode 100644 codex-launcher_2.7.0_all.deb diff --git a/CHANGELOG.md b/CHANGELOG.md index b2be983..8b70c8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## v2.7.0 (2026-05-20) + +- **Usage Dashboard redesigned** (inspired by OpenUsage design patterns) + - Deep Space dark theme with Catppuccin-inspired color palette + - Header with animated status dots (OK/WARN/ERR provider health) + - KPI summary strip: total providers, requests, token volume, avg latency + - Provider cards with colored borders matching health status + - Status pills: OK (green), WARN (yellow), ERR (red) + - Colored section separators per metric type (Usage=yellow, Models=lavender) + - Model composition bar: stacked horizontal segments per model share + - Per-model breakdown with mini progress bars, percentage, request counts + - Per-model token breakdown (in/out) when available + - Token formatting: 1.2M, 45.3K instead of raw numbers + - Duration formatting: 1.5h, 3.2m instead of raw seconds + - Error section with warning icon + +- **TCP_NODELAY streaming optimization** + - Disables Nagle's algorithm on streaming connections + - Reduces per-packet latency by up to 40ms on small SSE events + - Applied to all 4 streaming code paths (openai-compat, retry, command-code, generic) + +- **Anthropic prompt caching** + - System prompts now sent as `cache_control: ephemeral` structured format + - Enables Anthropic's automatic prompt caching (saves tokens + cost on repeated prompts) + ## v2.6.1 (2026-05-20) - **Google OAuth rebuilt to emulate Gemini CLI** diff --git a/codex-launcher_2.6.1_all.deb b/codex-launcher_2.6.1_all.deb deleted file mode 100644 index 0a72c2555f53641d94403b97c8634e109fb630ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34158 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 S;eBpOZ7@G$RWBi+!nO+1cqhOB diff --git a/codex-launcher_2.7.0_all.deb b/codex-launcher_2.7.0_all.deb new file mode 100644 index 0000000000000000000000000000000000000000..1ba20a6e0a865b6fbd02e072974a7a39112f7250 GIT binary patch literal 35098 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 TD@^B==GC)tEEfX#kqSEs$;|+( literal 0 HcmV?d00001 diff --git a/src/codex-launcher-gui b/src/codex-launcher-gui index 18e54da..26a9a1d 100755 --- a/src/codex-launcher-gui +++ b/src/codex-launcher-gui @@ -647,7 +647,7 @@ class LauncherWin(Gtk.Window): # header row hdr = Gtk.Box(spacing=8) vbox.pack_start(hdr, False, False, 0) - lbl = Gtk.Label(label="Codex Launcher v2.6.1") + lbl = Gtk.Label(label="Codex Launcher v2.7.0") lbl.set_use_markup(True) hdr.pack_start(lbl, False, False, 0) changelog_btn = Gtk.Button(label="Changelog") @@ -2481,10 +2481,17 @@ class BGPRouteDialog(Gtk.Dialog): 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", +_U = { + "base": "#0C0E16", "surface0": "#161928", "surface1": "#1E2235", + "surface2": "#2A2F47", "text": "#E4E6F0", "subtext": "#B0B4C8", + "dim": "#5C6180", "accent": "#7EB8F7", "blue": "#5DA4E8", + "sapphire": "#4EC5C1", "green": "#59D4A0", "yellow": "#F0C75E", + "red": "#F06A77", "peach": "#F09860", "teal": "#4EC5C1", + "lavender": "#A899F0", "sky": "#70C8E8", "maroon": "#C44B5C", + "flamingo": "#E878B0", "rosewater": "#F0D0C0", + "model_palette": ["#F09860", "#4EC5C1", "#5DA4E8", "#59D4A0", + "#F0C75E", "#A899F0", "#70C8E8", "#E878B0", + "#C44B5C", "#F0D0C0", "#7EB8F7", "#F06A77"], } _USAGE_STATS_FILE = HOME / ".cache/codex-proxy/usage-stats.json" @@ -2497,44 +2504,60 @@ def _load_usage_stats(): 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"] +def _fmt_tok(n): + if n >= 1_000_000: + return f"{n/1_000_000:.1f}M" + if n >= 1_000: + return f"{n/1_000:.1f}K" + return str(n) + +def _fmt_dur(s): + if s >= 3600: + return f"{s/3600:.1f}h" + if s >= 60: + return f"{s/60:.1f}m" + return f"{s:.1f}s" + +def _status_pill(success_rate, fail_pct): + if fail_pct > 0.15: + return ("ERR", _U["red"]) + if fail_pct > 0.05: + return ("WARN", _U["yellow"]) + return ("OK", _U["green"]) + +def _make_css_widget(css_str): + p = Gtk.CssProvider() + p.load_from_data(css_str.encode()) + return p + +def _apply_css(widget, css_str): + ctx = widget.get_style_context() + ctx.add_provider(_make_css_widget(css_str), Gtk.STYLE_PROVIDER_PRIORITY_USER) + class UsageWindow(Gtk.Window): def __init__(self, parent): - super().__init__(title="Usage Stats") + super().__init__(title="Usage Dashboard") self.set_transient_for(parent) - self.set_default_size(640, 560) + self.set_default_size(720, 640) self.set_position(Gtk.WindowPosition.CENTER) self._parent = parent + _apply_css(self, f""" + window {{ background-color: {_U["base"]}; }} + separator {{ background-color: {_U["surface1"]}; }} + """) + 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('Usage Dashboard') - 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('Never') - header.pack_end(self._updated_lbl, False, False, 8) - + self._build_header(vbox) + self._build_summary_strip(vbox) sep = Gtk.Separator() vbox.pack_start(sep, False, False, 0) - self._cards_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8) + self._cards_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) + self._cards_box.set_margin_top(8) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) sw.add(self._cards_box) @@ -2543,133 +2566,337 @@ class UsageWindow(Gtk.Window): self._refresh() self.show_all() + def _build_header(self, parent): + hdr = Gtk.Box(spacing=8) + hdr.set_margin_start(16) + hdr.set_margin_end(16) + hdr.set_margin_top(12) + hdr.set_margin_bottom(6) + parent.pack_start(hdr, False, False, 0) + + bolt = Gtk.Label() + bolt.set_markup(f'\u26A1') + hdr.pack_start(bolt, False, False, 0) + + title = Gtk.Label() + title.set_markup(f'Usage Dashboard') + hdr.pack_start(title, False, False, 0) + + self._status_dots = Gtk.Label() + hdr.pack_start(self._status_dots, False, False, 8) + + self._updated_lbl = Gtk.Label() + self._updated_lbl.set_markup(f'Never') + hdr.pack_end(self._updated_lbl, False, False, 4) + + refresh_btn = Gtk.Button(label="Refresh") + _apply_css(refresh_btn, f""" + button {{ color: {_U["text"]}; background-color: {_U["surface0"]}; + border: 1px solid {_U["surface1"]}; border-radius: 6px; padding: 4px 12px; }} + button:hover {{ background-color: {_U["surface1"]}; }} + """) + refresh_btn.connect("clicked", lambda b: self._refresh()) + hdr.pack_end(refresh_btn, False, False, 0) + + def _build_summary_strip(self, parent): + strip = Gtk.Box(spacing=0) + strip.set_margin_start(16) + strip.set_margin_end(16) + strip.set_margin_bottom(6) + _apply_css(strip, f"box {{ background-color: {_U["surface0"]}; border-radius: 8px; padding: 8px 12px; }}") + parent.pack_start(strip, False, False, 0) + + self._kpi_boxes = {} + for key, label, icon in [ + ("providers", "Providers", "\U0001F4CA"), + ("requests", "Requests", "\u26A1"), + ("tokens", "Tokens", "\U0001F9E0"), + ("latency", "Avg Latency", "\u23F1"), + ]: + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1) + lbl = Gtk.Label() + lbl.set_markup(f'{icon} {label}') + lbl.set_xalign(0) + box.pack_start(lbl, False, False, 0) + val = Gtk.Label() + val.set_markup(f'-') + val.set_xalign(0) + box.pack_start(val, False, False, 0) + box.set_margin_end(20) + strip.pack_start(box, False, False, 0) + self._kpi_boxes[key] = val + 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'Updated: {updated}') + self._updated_lbl.set_markup(f'{updated}') providers = stats.get("providers", {}) if not providers: empty = Gtk.Label() - empty.set_markup('No usage data yet.\nLaunch a session to start tracking.') + empty.set_markup(f'No usage data yet.\nLaunch a session to start tracking.') empty.set_margin_top(60) self._cards_box.pack_start(empty, False, False, 0) self._cards_box.show_all() return + total_req = 0 + total_tok_in = 0 + total_tok_out = 0 + total_dur = 0.0 + n_ok = 0 + n_warn = 0 + n_err = 0 + sorted_providers = sorted(providers.items(), key=lambda x: x[1].get("total_requests", 0), reverse=True) + for prov_name, prov_data in sorted_providers: + t = prov_data.get("total_requests", 0) + total_req += t + total_tok_in += prov_data.get("total_tokens_in", 0) + total_tok_out += prov_data.get("total_tokens_out", 0) + total_dur += prov_data.get("total_duration_s", 0.0) + fail = prov_data.get("failures", 0) + fail_pct = fail / t if t > 0 else 0 + _, sc = _status_pill(0, fail_pct) + if fail_pct > 0.15: + n_err += 1 + elif fail_pct > 0.05: + n_warn += 1 + else: + n_ok += 1 + + self._kpi_boxes["providers"].set_markup( + f'{len(providers)}') + self._kpi_boxes["requests"].set_markup( + f'{total_req:,}') + tok_sum = total_tok_in + total_tok_out + tok_str = f"{_fmt_tok(tok_sum)} in:{_fmt_tok(total_tok_in)} out:{_fmt_tok(total_tok_out)}" if tok_sum else "N/A" + self._kpi_boxes["tokens"].set_markup( + f'{tok_str}') + avg_lat = total_dur / total_req if total_req > 0 else 0 + self._kpi_boxes["latency"].set_markup( + f'{_fmt_dur(avg_lat)}') + + dots_parts = [] + if n_ok: + dots_parts.append(f'\u25CF{n_ok}') + if n_warn: + dots_parts.append(f'\u25D0{n_warn}') + if n_err: + dots_parts.append(f'\u2717{n_err}') + if dots_parts: + self._status_dots.set_markup(" ".join(dots_parts)) + 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) + card = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + card.set_margin_start(12) + card.set_margin_end(12) + _apply_css(card, f""" + box {{ background-color: {_U["surface0"]}; border-radius: 10px; + border: 1px solid {_U["surface1"]}; }} + """) 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 + fail_pct = fail / total if total > 0 else 0 + status_text, status_color = _status_pill(success_rate, fail_pct) + + border_color = status_color + _apply_css(card, f""" + box {{ background-color: {_U["surface0"]}; border-radius: 10px; + border: 1px solid {border_color}; }} + """) + + inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=3) + inner.set_margin_start(14) + inner.set_margin_end(14) + inner.set_margin_top(10) + inner.set_margin_bottom(10) + card.pack_start(inner, False, False, 0) + + top = Gtk.Box(spacing=6) + inner.pack_start(top, False, False, 0) + + dot = Gtk.Label() + dot.set_markup(f'\u25CF') + top.pack_start(dot, False, False, 0) name_lbl = Gtk.Label() short = name.replace("https://", "").replace("http://", "").split("/")[0] - name_lbl.set_markup(f'{short}') - top_row.pack_start(name_lbl, False, False, 0) + name_lbl.set_markup(f'{short}') + top.pack_start(name_lbl, False, False, 0) + + pill = Gtk.Label() + pill.set_markup(f' {status_text} ') + top.pack_start(pill, False, False, 4) req_lbl = Gtk.Label() - req_lbl.set_markup(f'{total} requests') - top_row.pack_start(req_lbl, False, False, 8) - - if fail > 0: - err_lbl = Gtk.Label() - err_lbl.set_markup(f'{fail} failed') - top_row.pack_start(err_lbl, False, False, 4) + req_lbl.set_markup(f'{total} req') + top.pack_start(req_lbl, False, False, 6) last_used = data.get("last_used", "") if last_used: lu_lbl = Gtk.Label() - lu_lbl.set_markup(f'{last_used}') - top_row.pack_end(lu_lbl, False, False, 0) + lu_lbl.set_markup(f'{last_used}') + top.pack_end(lu_lbl, False, False, 0) + + sep1 = Gtk.Separator() + _apply_css(sep1, f"separator {{ background-color: {status_color}; margin-top: 4px; }}") + inner.pack_start(sep1, False, False, 0) + + gauge_box = Gtk.Box(spacing=4) + gauge_box.set_margin_top(4) + inner.pack_start(gauge_box, False, False, 0) + + gauge_label = Gtk.Label() + gauge_label.set_markup(f'\u26A1') + gauge_box.pack_start(gauge_label, 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_text(f"{bar_pct}%") 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) + bar_css = f""" + progress {{ background-color: {status_color}; border-radius: 6px; }} + trough {{ background-color: {_U["surface1"]}; border-radius: 6px; min-height: 12px; }} + """ + _apply_css(bar, bar_css) + bar.set_hexpand(True) + gauge_box.pack_start(bar, True, True, 0) - # Stats row - stats_row = Gtk.Box(spacing=16) - outer.pack_start(stats_row, False, False, 0) + if fail > 0: + fail_lbl = Gtk.Label() + fail_lbl.set_markup(f'{fail} fail') + gauge_box.pack_end(fail_lbl, False, False, 0) + + metrics_box = Gtk.Box(spacing=0) + metrics_box.set_margin_top(4) + inner.pack_start(metrics_box, 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"), + for label, value, color in [ + ("Tokens In", f"{_fmt_tok(t_in)}", _U["sapphire"]), + ("Tokens Out", f"{_fmt_tok(t_out)}", _U["peach"]), + ("Avg Latency", _fmt_dur(avg_dur), _U["sky"]), + ("Duration", _fmt_dur(dur), _U["lavender"]), ]: - box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1) + box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) l = Gtk.Label() - l.set_markup(f'{label}') + l.set_markup(f'{label}') + l.set_xalign(0) box.pack_start(l, False, False, 0) v = Gtk.Label() - v.set_markup(f'{value}') + v.set_markup(f'{value}') + v.set_xalign(0) box.pack_start(v, False, False, 0) - stats_row.pack_start(box, False, False, 0) + box.set_margin_end(16) + metrics_box.pack_start(box, False, False, 0) - # Models breakdown models = data.get("models", {}) - if len(models) > 0: - model_str = " ".join( - f'{m} ' - f'({md.get("requests",0)})' - 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'Models: {model_str}') - m_lbl.set_line_wrap(True) - m_lbl.set_xalign(0) - outer.pack_start(m_lbl, False, False, 2) + if models: + self._build_models_section(inner, models, total) - # Error info last_err = data.get("last_error") if last_err: + err_box = Gtk.Box(spacing=4) + err_box.set_margin_top(4) + inner.pack_start(err_box, False, False, 0) + icon = Gtk.Label() + icon.set_markup(f'\u26A0') + err_box.pack_start(icon, False, False, 0) err_lbl = Gtk.Label() - err_lbl.set_markup(f'Last error: {last_err}') + err_lbl.set_markup(f'{last_err}') err_lbl.set_xalign(0) - outer.pack_start(err_lbl, False, False, 0) + err_lbl.set_line_wrap(True) + err_box.pack_start(err_lbl, False, False, 0) - return frame + return card + + def _build_models_section(self, parent, models, total_req): + sep_m = Gtk.Separator() + _apply_css(sep_m, f"separator {{ background-color: {_U["lavender"]}; margin-top: 4px; margin-bottom: 2px; }}") + parent.pack_start(sep_m, False, False, 0) + + header = Gtk.Box(spacing=4) + header.set_margin_top(2) + parent.pack_start(header, False, False, 0) + icon = Gtk.Label() + icon.set_markup(f'\U0001F916') + header.pack_start(icon, False, False, 0) + lbl = Gtk.Label() + lbl.set_markup(f'Models') + header.pack_start(lbl, False, False, 0) + + sorted_models = sorted(models.items(), key=lambda x: x[1].get("requests", 0), reverse=True) + + if total_req > 0: + comp_bar = Gtk.Box(spacing=0) + _apply_css(comp_bar, f"box {{ background-color: {_U["surface1"]}; border-radius: 4px; min-height: 8px; margin-top: 2px; }}") + parent.pack_start(comp_bar, False, False, 0) + for i, (mname, mdata) in enumerate(sorted_models): + m_req = mdata.get("requests", 0) + pct = m_req / total_req + if pct < 0.01: + continue + seg = Gtk.Box() + color = _U["model_palette"][i % len(_U["model_palette"])] + _apply_css(seg, f"box {{ background-color: {color}; min-height: 8px; }}") + seg.set_size_request(max(int(pct * 400), 4), 8) + comp_bar.pack_start(seg, False, False, 0) + + models_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=1) + models_box.set_margin_top(2) + parent.pack_start(models_box, False, False, 0) + + for i, (mname, mdata) in enumerate(sorted_models[:6]): + row = Gtk.Box(spacing=6) + models_box.pack_start(row, False, False, 0) + color = _U["model_palette"][i % len(_U["model_palette"])] + dot = Gtk.Label() + dot.set_markup(f'\u25CF') + row.pack_start(dot, False, False, 0) + m_lbl = Gtk.Label() + m_lbl.set_markup(f'{mname}') + m_lbl.set_xalign(0) + m_lbl.set_size_request(120, -1) + row.pack_start(m_lbl, False, False, 0) + + m_req = mdata.get("requests", 0) + pct = m_req / total_req * 100 if total_req > 0 else 0 + + m_bar = Gtk.ProgressBar() + m_bar.set_fraction(m_req / total_req if total_req > 0 else 0) + _apply_css(m_bar, f""" + progress {{ background-color: {color}; border-radius: 3px; }} + trough {{ background-color: {_U["surface1"]}; border-radius: 3px; min-height: 6px; }} + """) + m_bar.set_size_request(80, -1) + row.pack_start(m_bar, False, False, 0) + + pct_lbl = Gtk.Label() + pct_lbl.set_markup(f'{pct:.0f}% ({m_req})') + row.pack_start(pct_lbl, False, False, 0) + + m_in = mdata.get("tokens_in", 0) + m_out = mdata.get("tokens_out", 0) + if m_in or m_out: + tok_lbl = Gtk.Label() + tok_lbl.set_markup(f'in:{_fmt_tok(m_in)} out:{_fmt_tok(m_out)}') + row.pack_end(tok_lbl, False, False, 0) def main(): diff --git a/src/translate-proxy.py b/src/translate-proxy.py index 1d2442e..aa087f4 100755 --- a/src/translate-proxy.py +++ b/src/translate-proxy.py @@ -11,7 +11,7 @@ Usage: python3 translate-proxy.py --backend openai-compat --target-url https://... --api-key sk-... """ -import json, http.server, urllib.request, time, uuid, os, sys, argparse +import json, http.server, urllib.request, time, uuid, os, sys, argparse, threading, socket # ═══════════════════════════════════════════════════════════════════ # Config @@ -141,6 +141,8 @@ _pool = uuid.uuid4().hex[:8] _response_store = {} _MAX_STORED = 50 +_LOG_DIR = os.path.join(os.path.expanduser("~"), ".cache", "codex-proxy") +os.makedirs(_LOG_DIR, exist_ok=True) _stats_path = os.path.join(_LOG_DIR, "usage-stats.json") _stats_lock = threading.Lock() @@ -961,9 +963,6 @@ def cc_stream_to_sse(cc_stream, model, req_id): # HTTP Server # ═══════════════════════════════════════════════════════════════════ -_LOG_DIR = os.path.join(os.path.expanduser("~"), ".cache", "codex-proxy") -os.makedirs(_LOG_DIR, exist_ok=True) - def _log_resp(resp_id, status, output): try: import datetime as _dt @@ -1209,6 +1208,11 @@ class Handler(http.server.BaseHTTPRequestHandler): self.send_header("Cache-Control", "no-cache") self.send_header("Connection", "keep-alive") self.end_headers() + if hasattr(self, 'connection') and self.connection: + try: + self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except Exception: + pass collected_events = [] last_resp_id = None @@ -1290,6 +1294,11 @@ class Handler(http.server.BaseHTTPRequestHandler): self.send_header("Cache-Control", "no-cache") self.send_header("Connection", "keep-alive") self.end_headers() + if hasattr(self, 'connection') and self.connection: + try: + self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except Exception: + pass last_resp_id = None last_output = None @@ -1322,7 +1331,8 @@ class Handler(http.server.BaseHTTPRequestHandler): "max_tokens": body.get("max_output_tokens", 8192)} instructions = body.get("instructions", "").strip() if instructions: - an_body["system"] = instructions + an_body["system"] = [{"type": "text", "text": instructions, + "cache_control": {"type": "ephemeral"}}] for k in ("temperature", "top_p"): if k in body: an_body[k] = body[k] @@ -1429,6 +1439,11 @@ class Handler(http.server.BaseHTTPRequestHandler): self.send_header("Cache-Control", "no-cache") self.send_header("Connection", "keep-alive") self.end_headers() + if hasattr(self, 'connection') and self.connection: + try: + self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except Exception: + pass last_resp_id = None last_output = None for event in cc_stream_to_sse(upstream, model, body.get("request_id") or body.get("id")): @@ -1476,6 +1491,11 @@ class Handler(http.server.BaseHTTPRequestHandler): self.send_header("Cache-Control", "no-cache") self.send_header("Connection", "keep-alive") self.end_headers() + if hasattr(self, 'connection') and self.connection: + try: + self.connection.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + except Exception: + pass last_resp_id = None last_output = None last_status = None