diff --git a/CHANGELOG.md b/CHANGELOG.md
index ace2e99..72c44cf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
# Changelog
+## v2.2.0 (2026-05-20)
+
+- **Added per-provider Reasoning controls in endpoint editor**
+ - Reasoning On/Off toggle — disable reasoning for models that exhaust output tokens (e.g., Crof mimo-v2.5-pro)
+ - Reasoning Effort selector: None, Minimal, Low, Medium, High, Max
+ - When reasoning is OFF: sends `enable_thinking=false` + `reasoning_effort=none` to upstream API
+ - When reasoning is ON: sends user-selected effort level (default: Medium)
+ - Settings stored per-endpoint, passed through proxy config to upstream requests
+- Strip `reasoning_content` from proxy output — Codex doesn't use it, avoids token waste
+- Force `max_tokens=64000` minimum for openai-compat providers — room for both reasoning and content
+- Inspired by unsloth's reasoning control patterns for Qwen/GPT-OSS models
+
## v2.1.3 (2026-05-19)
- **Fixed Crof mimo-v2.5-pro stopping mid-response (finish_reason=length)**
diff --git a/codex-launcher_2.1.3_all.deb b/codex-launcher_2.1.3_all.deb
deleted file mode 100644
index 7245e9c..0000000
Binary files a/codex-launcher_2.1.3_all.deb and /dev/null differ
diff --git a/codex-launcher_2.2.0_all.deb b/codex-launcher_2.2.0_all.deb
new file mode 100644
index 0000000..65a01f5
Binary files /dev/null and b/codex-launcher_2.2.0_all.deb differ
diff --git a/src/codex-launcher-gui b/src/codex-launcher-gui
index f9de114..3f8dad2 100755
--- a/src/codex-launcher-gui
+++ b/src/codex-launcher-gui
@@ -24,6 +24,15 @@ model_catalog_json = ""
"""
CHANGELOG = [
+ ("2.2.0", "2026-05-20", [
+ "Added per-provider Reasoning On/Off toggle in endpoint editor",
+ "Added Reasoning Effort level per provider: None, Minimal, Low, Medium, High, Max",
+ "When reasoning is OFF: sends enable_thinking=false + reasoning_effort=none to upstream API",
+ "When reasoning is ON: sends user-selected effort level (default: Medium)",
+ "Fixes Crof mimo-v2.5-pro and similar reasoning models exhausting output tokens",
+ "Strip reasoning_content from proxy output — Codex doesn't use it",
+ "Force max_tokens=64000 minimum for openai-compat providers",
+ ]),
("2.1.3", "2026-05-19", [
"Fixed Crof mimo-v2.5-pro stopping: reasoning_content exhausted all output tokens",
"Strip reasoning_content from proxy output — Codex doesn't use it, avoids token waste",
@@ -434,6 +443,8 @@ def _start_proxy_for(endpoint, logfn):
"target_url": normalize_base_url(endpoint["base_url"]),
"api_key": endpoint["api_key"],
"cc_version": endpoint.get("cc_version", ""),
+ "reasoning_enabled": endpoint.get("reasoning_enabled", True),
+ "reasoning_effort": endpoint.get("reasoning_effort", "medium"),
"models": [{"id": m, "object": "model", "created": 1700000000, "owned_by": endpoint["name"]}
for m in endpoint.get("models", [])],
}
@@ -532,7 +543,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.1.3")
+ lbl = Gtk.Label(label="Codex Launcher v2.2.0")
lbl.set_use_markup(True)
hdr.pack_start(lbl, False, False, 0)
changelog_btn = Gtk.Button(label="Changelog")
@@ -1371,7 +1382,7 @@ class EditEndpointDialog(Gtk.Dialog):
"base_url": "", "api_key": "", "default_model": "", "models": [],
"provider_preset": "Custom",
}
- self.set_default_size(480, 420)
+ self.set_default_size(480, 520)
area = self.get_content_area()
area.set_spacing(6)
@@ -1419,6 +1430,20 @@ class EditEndpointDialog(Gtk.Dialog):
self._entry_cc_ver.set_placeholder_text("e.g. 0.26.8 (Command Code only)")
add_row(5, "CC Version:", self._entry_cc_ver)
+ self._switch_reasoning = Gtk.Switch()
+ self._switch_reasoning.set_active(self._data.get("reasoning_enabled", True))
+ self._switch_reasoning.connect("notify::active", lambda *a: self._on_reasoning_toggled())
+ add_row(6, "Reasoning:", self._switch_reasoning)
+
+ self._combo_effort = Gtk.ComboBoxText()
+ for ev, el in [("none", "None"), ("minimal", "Minimal"), ("low", "Low"),
+ ("medium", "Medium"), ("high", "High"), ("max", "Max")]:
+ self._combo_effort.append(ev, el)
+ saved_effort = self._data.get("reasoning_effort", "medium")
+ self._combo_effort.set_active_id(saved_effort if saved_effort in ("none","minimal","low","medium","high","max") else "medium")
+ add_row(7, "Effort:", self._combo_effort)
+ self._on_reasoning_toggled()
+
# Models
mlbl = Gtk.Label(label="Models:", xalign=0)
area.pack_start(mlbl, False, False, 4)
@@ -1522,6 +1547,10 @@ class EditEndpointDialog(Gtk.Dialog):
if initial and self._data.get("models"):
self._refresh_default_combo(self._data.get("default_model", ""))
+ def _on_reasoning_toggled(self, *_):
+ active = self._switch_reasoning.get_active()
+ self._combo_effort.set_sensitive(active)
+
def _remove_model(self, path):
current = self._combo_default.get_active_text()
self._model_store.remove(self._model_store.get_iter(path))
@@ -1620,6 +1649,8 @@ class EditEndpointDialog(Gtk.Dialog):
cc_ver = self._entry_cc_ver.get_text().strip()
if cc_ver:
new_ep["cc_version"] = cc_ver
+ new_ep["reasoning_enabled"] = self._switch_reasoning.get_active()
+ new_ep["reasoning_effort"] = self._combo_effort.get_active_id() or "medium"
new_ep["base_url"] = normalize_base_url(new_ep["base_url"])
# Update or append
diff --git a/src/translate-proxy.py b/src/translate-proxy.py
index ffb2d33..bfcd59b 100755
--- a/src/translate-proxy.py
+++ b/src/translate-proxy.py
@@ -81,6 +81,8 @@ TARGET_URL = CONFIG["target_url"].rstrip("/")
API_KEY = CONFIG["api_key"]
MODELS = CONFIG["models"]
CC_VERSION = CONFIG.get("cc_version", "")
+REASONING_ENABLED = CONFIG.get("reasoning_enabled", True)
+REASONING_EFFORT = CONFIG.get("reasoning_effort", "medium")
# ═══════════════════════════════════════════════════════════════════
# Shared helpers
@@ -889,6 +891,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
if body.get("tool_choice"):
chat_body["tool_choice"] = body["tool_choice"]
chat_body["stream"] = stream
+ if not REASONING_ENABLED:
+ chat_body["enable_thinking"] = False
+ chat_body["reasoning_effort"] = REASONING_EFFORT if REASONING_ENABLED else "none"
target = upstream_target(TARGET_URL, "/chat/completions")
fwd = forwarded_headers(self.headers, {