v2.2.1: fix compaction orphaning tool outputs causing Crof incomplete

- Fix compaction cutting between function_call and function_call_output pairs
- Orphaned tool results confused Crof models causing finish_reason=length
- reasoning_effort=none now always sends enable_thinking=false too
- Added Crof upstream debug logging
This commit is contained in:
Roman
2026-05-20 13:14:40 +04:00
Unverified
parent 9bcb6998e0
commit 6c67642522
5 changed files with 40 additions and 5 deletions

View File

@@ -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**

Binary file not shown.

Binary file not shown.

View File

@@ -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="<b>Codex Launcher v2.2.0</b>")
lbl = Gtk.Label(label="<b>Codex Launcher v2.2.1</b>")
lbl.set_use_markup(True)
hdr.pack_start(lbl, False, False, 0)
changelog_btn = Gtk.Button(label="Changelog")

View File

@@ -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(),