diff --git a/CHANGELOG.md b/CHANGELOG.md index 23ecd76..eefa18d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## v2.2.1 (2026-05-20) + +- **Fixed compaction orphaning function_call_output items** — root cause of Crof `incomplete` responses + - Compaction cut between function_call and its function_call_output, creating dangling tool results + - Crof model received orphaned `tool` messages with empty `tool_call_id`, causing confusion and token exhaustion + - Compaction now expands tail boundary to include matching function_call/function_call_output pairs +- **Fixed reasoning control**: `reasoning_effort=none` now always sends both `enable_thinking=false` AND `reasoning_effort=none` + - Crof API testing confirmed `reasoning_effort=none` is what actually suppresses reasoning, not `enable_thinking=false` +- Added upstream debug logging to `~/.cache/codex-proxy/crof-upstream.jsonl` + ## v2.2.0 (2026-05-20) - **Added per-provider Reasoning controls in endpoint editor** diff --git a/codex-launcher_2.2.0_all.deb b/codex-launcher_2.2.0_all.deb deleted file mode 100644 index 1a10bc3..0000000 Binary files a/codex-launcher_2.2.0_all.deb and /dev/null differ diff --git a/codex-launcher_2.2.1_all.deb b/codex-launcher_2.2.1_all.deb new file mode 100644 index 0000000..1bf05c8 Binary files /dev/null and b/codex-launcher_2.2.1_all.deb differ diff --git a/src/codex-launcher-gui b/src/codex-launcher-gui index c59ee71..1757f72 100755 --- a/src/codex-launcher-gui +++ b/src/codex-launcher-gui @@ -24,6 +24,11 @@ model_catalog_json = "" """ CHANGELOG = [ + ("2.2.1", "2026-05-20", [ + "Fixed compaction orphaning function_call_output items — root cause of Crof incomplete responses", + "Compaction now respects function_call/function_call_output pairs — no more dangling tool results", + "Fixed reasoning control: reasoning_effort=none now always sends enable_thinking=false too", + ]), ("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", @@ -543,7 +548,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.2.0") + lbl = Gtk.Label(label="Codex Launcher v2.2.1") lbl.set_use_markup(True) hdr.pack_start(lbl, False, False, 0) changelog_btn = Gtk.Button(label="Changelog") diff --git a/src/translate-proxy.py b/src/translate-proxy.py index bfcd59b..e3883ea 100755 --- a/src/translate-proxy.py +++ b/src/translate-proxy.py @@ -238,8 +238,16 @@ def _compact_input(input_data): break head = input_data[:head_end] - tail = input_data[-_COMPACT_KEEP_RECENT:] - body = input_data[head_end:-_COMPACT_KEEP_RECENT] + tail_start = len(input_data) - _COMPACT_KEEP_RECENT + while tail_start > head_end: + if input_data[tail_start].get("type") == "function_call_output": + tail_start -= 1 + elif input_data[tail_start].get("type") == "message" and input_data[tail_start].get("role") == "assistant": + tail_start -= 1 + else: + break + tail = input_data[tail_start:] + body = input_data[head_end:tail_start] if not body: return head + tail @@ -891,9 +899,11 @@ 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: + if not REASONING_ENABLED or REASONING_EFFORT == "none": chat_body["enable_thinking"] = False - chat_body["reasoning_effort"] = REASONING_EFFORT if REASONING_ENABLED else "none" + chat_body["reasoning_effort"] = "none" + else: + chat_body["reasoning_effort"] = REASONING_EFFORT target = upstream_target(TARGET_URL, "/chat/completions") fwd = forwarded_headers(self.headers, { @@ -901,6 +911,16 @@ class Handler(http.server.BaseHTTPRequestHandler): "Authorization": f"Bearer {API_KEY}", }, browser_ua=True) print(f"[translate-proxy] POST {target} model={model} stream={stream} ua={fwd.get('User-Agent','')[:50]}", file=sys.stderr) + _crof_debug_path = os.path.join(_LOG_DIR, "crof-upstream.jsonl") + with open(_crof_debug_path, "a") as _cdf: + _cdf.write(json.dumps({ + "ts": _ts, "model": model, "max_tokens": chat_body.get("max_tokens"), + "reasoning_enabled": REASONING_ENABLED, "reasoning_effort": chat_body.get("reasoning_effort"), + "enable_thinking": chat_body.get("enable_thinking", "NOT_SENT"), + "n_messages": len(chat_body.get("messages", [])), + "has_tools": bool(chat_body.get("tools")), + "messages_summary": [{"role": m.get("role"), "tc": len(m.get("tool_calls", [])), "content_len": len(str(m.get("content", "")))[:6], "tool_call_id": m.get("tool_call_id")} for m in chat_body.get("messages", [])], + }) + "\n") req = urllib.request.Request( target, data=json.dumps(chat_body).encode(),