From aa377024d9dfbf0cf7826902195e5f77c8f48b32 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 19 May 2026 21:25:35 +0400 Subject: [PATCH] fix: Crof multi-turn tool calls + auto-trim long conversations Root cause: Codex sends function_call items with id=None, causing tool_call_id mismatch between tool calls and tool results. Proxy now resolves IDs by call_id + positional fallback. Auto-trim: conversations exceeding 30 items are trimmed automatically, keeping system messages, original user query, and most recent items. This prevents context overflow on providers with smaller context windows (Crof mimo-v2.5-pro stops responding at ~40 items). - Fix None tool IDs in oa_input_to_messages with positional matching - Auto-trim input to 30 items max (keeps head + tail) - Add request/response logging to ~/.cache/codex-proxy/requests.log - Proxy stderr visible in launcher terminal for debugging - v2.1.2 --- CHANGELOG.md | 13 +++-- codex-launcher_2.1.2_all.deb | Bin 23240 -> 24176 bytes src/codex-launcher-gui | 13 ++--- src/translate-proxy.py | 99 ++++++++++++++++++++++++++++++++--- 4 files changed, 108 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c3c5d6..a47b8dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,15 @@ ## v2.1.2 (2026-05-19) -- **Fixed Crof.ai and other providers stopping after first tool call (root cause)** -- Proxy now stores responses and resolves `previous_response_id` for multi-turn conversations -- Codex Desktop uses `previous_response_id` to chain turns — proxy reconstructs full conversation context -- Without this fix, the proxy sent only the new `function_call_output` to upstream without the original user message or assistant tool call, causing the upstream model to return incomplete responses +- **Fixed Crof.ai and providers stopping after first tool call (root cause: None tool IDs)** +- Codex sends `function_call` items with `id=None` — proxy now matches tool results to calls by call_id + positional fallback - Fixed orphan message output item when response is only tool calls (no text content) -- Response store capped at 50 entries (LRU eviction) +- **Auto-trims long conversations (>30 items)** to prevent context overflow on providers like Crof + - Keeps system/developer messages, original user query, and most recent items + - Drops oldest tool call/outputs from the middle when conversation grows too long + - Prevents `status=incomplete` errors on providers with smaller context windows +- Added request/response logging to `~/.cache/codex-proxy/requests.log` for debugging +- Proxy stderr no longer discarded by launcher (visible in terminal for debugging) ## v2.1.1 (2026-05-19) diff --git a/codex-launcher_2.1.2_all.deb b/codex-launcher_2.1.2_all.deb index 7a98692809b251da3905ed8ee803523923cf0651..4837cff71d7ee82b1ccb51505611270b351bf02d 100644 GIT binary patch delta 23153 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 z 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

kqW#VpGhWU zaiWsmhP_+;q+(u%Tw1Ah!rg&_1F+7)V<=S@Q*7!~vY&aGl_*|@&VfclBD;%!)|55_ z401h%4l0VfXv+#%4??s(SV$~NHiXlRIs&7WmD6lKvs(({DBvj77=C#qio8~TlL6Jc z9JgGAdrVd|=jditysl~aS!oe!JyDDFLP@kFL#)H_Lf-Lx1b;ZzoP9z(bNPb{)R8sL z4NyOVnIc%wmi#R?tB&clxcg3jy#tV&0$8n&q8X8#a;SPvEy&2IimDNOS$ouKjl&XzM^;AtGfC8+P1HO8`)F z@6K%E9m8Dy*oJdgM_lcHw%L}R@rwRp>jXYQbhek~Z`tSeyrx~mTT%G%3n-o=*V`=A zKLM*LTgW3R(vWp`aT$!)U>k_ZWzjh8-C)i#GfEV0tfGP#Vmlb2y7YfY=Ec_2H1=la1e{@CHy~B z8jP02!V{rcuWVy~WtGxx4xi9TMWitZIp`n~+NY-@e%{s?vM!r9^_%76qGY7|Df>8? zF7HaA+_8MK4n%3uV?V*s)oY+l&Y{LFp3+Yo6)IMXJP`J?x@{sC-(3@ybpD*!iRn)P z-#X-vP5CVg`Gi6FbsT6AOwM#U(VhYbi{~oO0x20~n{{7*gshsVD_2#B*3@rUp?P?T zuUYJ-U`JPlQ&7+KxjBm})Fj7p05*U{j%Uje-<}szun=d$aY$^XdA2>OM%+aYCbpWNUqC02sL{_vB=8^= zbr0PC1d{%L0g@hH)WzEXq8VVr{(xL_i~;cvxEJBiG)4(LP$TONaFO<&VgT0GTPy|` zdKCd3L&7z02605pUrWC{=-!?^$e diff --git a/src/codex-launcher-gui b/src/codex-launcher-gui index 3f12e86..9753029 100755 --- a/src/codex-launcher-gui +++ b/src/codex-launcher-gui @@ -25,10 +25,11 @@ model_catalog_json = "" CHANGELOG = [ ("2.1.2", "2026-05-19", [ - "Fixed Crof.ai and other providers stopping after first tool call", - "Proxy now stores and resolves previous_response_id for multi-turn conversations", - "Codex Desktop uses previous_response_id to chain turns — proxy reconstructs full context", - "Fixed orphan message output item when response is only tool calls (no text)", + "Fixed Crof.ai and providers stopping after first tool call (root cause: None tool IDs)", + "Codex sends function_call items with id=None — proxy now matches tool results to calls by position", + "Fixed orphan message output item when response has only tool calls (no text)", + "Auto-trims long conversations (>30 items) to prevent context overflow on providers like Crof", + "Added request/response logging to ~/.cache/codex-proxy/requests.log", ]), ("2.1.1", "2026-05-19", [ "Fixed proxy: map 'developer' role to 'system' for Chat Completions providers", @@ -437,7 +438,7 @@ def _start_proxy_for(endpoint, logfn): _proxy_proc = subprocess.Popen( ["python3", str(PROXY), "--config", str(pcfg_path)], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, + stdout=subprocess.DEVNULL, preexec_fn=os.setsid, ) @@ -526,7 +527,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.1.2") 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 f2cefa4..6ea3743 100755 --- a/src/translate-proxy.py +++ b/src/translate-proxy.py @@ -165,6 +165,28 @@ def forwarded_headers(request_headers, extra=None, browser_ua=False): headers.update(extra) return headers +_MAX_INPUT_ITEMS = 30 + +def _trim_input(input_data): + if not isinstance(input_data, list) or len(input_data) <= _MAX_INPUT_ITEMS: + return input_data + head_end = 0 + for i, item in enumerate(input_data): + t = item.get("type") + if t == "message" and item.get("role") in ("developer", "system"): + head_end = i + 1 + elif t == "message" and item.get("role") == "user" and head_end == i: + head_end = i + 1 + else: + break + head = input_data[:head_end] + tail_keep = _MAX_INPUT_ITEMS - len(head) + tail = input_data[-tail_keep:] + trimmed = len(input_data) - len(head) - len(tail) + if trimmed > 0: + print(f"[trim] {len(input_data)} items -> {len(head) + len(tail)} (dropped {trimmed} old items)", file=sys.stderr) + return head + tail + # ═══════════════════════════════════════════════════════════════════ # OpenAI-compat backend # ═══════════════════════════════════════════════════════════════════ @@ -175,16 +197,19 @@ def oa_input_to_messages(input_data): msgs.append({"role": "user", "content": input_data}) elif isinstance(input_data, list): pending_tool_calls = [] + last_flushed_ids = [] for item in input_data: t = item.get("type") if t == "function_call": + tcid = item.get("call_id") or item.get("id") or uid("tc") pending_tool_calls.append( - {"id": item.get("call_id", item.get("id", uid("tc"))), + {"id": tcid, "type": "function", "function": {"name": item.get("name", ""), "arguments": item.get("arguments", "{}")}}) continue if pending_tool_calls: + last_flushed_ids = [tc["id"] for tc in pending_tool_calls] msgs.append({"role": "assistant", "content": None, "tool_calls": pending_tool_calls}) pending_tool_calls = [] if t == "message": @@ -205,7 +230,12 @@ def oa_input_to_messages(input_data): if text is not None: msgs.append({"role": role, "content": text}) elif t == "function_call_output": - msgs.append({"role": "tool", "tool_call_id": item.get("id", ""), + tcid = item.get("call_id") or item.get("id") or "" + if not tcid and last_flushed_ids: + idx = len([m for m in msgs if m.get("role") == "tool"]) + if idx < len(last_flushed_ids): + tcid = last_flushed_ids[idx] + msgs.append({"role": "tool", "tool_call_id": tcid, "content": item.get("output", "")}) if pending_tool_calls: msgs.append({"role": "assistant", "content": None, "tool_calls": pending_tool_calls}) @@ -654,6 +684,29 @@ 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 + _lp = os.path.join(_LOG_DIR, "requests.log") + with open(_lp, "a") as _f: + _f.write(f" RESPONSE id={resp_id} status={status}\n") + if output: + for o in output: + ot = o.get("type") + if ot == "message": + _f.write(f" -> message: {o.get('content',[{}])[0].get('text','')[:200]}\n") + elif ot == "function_call": + _f.write(f" -> function_call: {o.get('name')}({o.get('arguments','')[:120]})\n") + else: + _f.write(f" -> {ot}\n") + _f.write(f"{'='*60}\n") + _f.flush() + except Exception: + pass + class Handler(http.server.BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" @@ -669,6 +722,8 @@ class Handler(http.server.BaseHTTPRequestHandler): else: self.send_error(404) + _logf = None + def _handle(self): try: clen = int(self.headers.get("Content-Length", 0)) @@ -676,11 +731,39 @@ class Handler(http.server.BaseHTTPRequestHandler): except Exception as e: return self.send_json(400, {"error": {"message": f"Bad request: {e}"}}) - input_data = resolve_previous_response(body) - body["input"] = input_data + import datetime as _dt + _log_path = os.path.join(_LOG_DIR, "requests.log") + _ts = _dt.datetime.now().isoformat() + prev_id = body.get("previous_response_id") - input_types = [i.get("type") for i in input_data] if isinstance(input_data, list) else str(type(input_data)) - print(f"[REQUEST] prev_id={prev_id} resolved_input_types={input_types}", file=sys.stderr) + raw_input = body.get("input", "") + input_data = resolve_previous_response(body) + input_data = _trim_input(input_data) + body["input"] = input_data + + raw_types = [i.get("type") for i in raw_input] if isinstance(raw_input, list) else "str" + resolved_types = [i.get("type") for i in input_data] if isinstance(input_data, list) else "str" + + print(f"[REQUEST] prev_id={prev_id} raw={raw_types} resolved={resolved_types}", file=sys.stderr) + with open(_log_path, "a") as _lf: + _lf.write(f"\n{'='*60}\n{_ts} REQUEST {self.path}\n") + _lf.write(f" prev_id={prev_id}\n") + _lf.write(f" raw_input_types={raw_types}\n") + _lf.write(f" resolved_input_types={resolved_types}\n") + _lf.write(f" stream={body.get('stream')} model={body.get('model')}\n") + _lf.write(f" store_keys={list(_response_store.keys())}\n") + if isinstance(input_data, list): + for i, item in enumerate(input_data): + t = item.get("type") + if t == "message": + _lf.write(f" [{i}] message role={item.get('role')} text={str(item.get('content',''))[:120]}\n") + elif t == "function_call": + _lf.write(f" [{i}] function_call call_id={item.get('call_id')} id={item.get('id')} name={item.get('name')} args={item.get('arguments','')[:120]}\n") + elif t == "function_call_output": + _lf.write(f" [{i}] function_call_output id={item.get('id')} output={str(item.get('output',''))[:120]}\n") + else: + _lf.write(f" [{i}] {t}\n") + _lf.flush() model = body.get("model", MODELS[0]["id"] if MODELS else "unknown") stream = body.get("stream", False) @@ -887,6 +970,7 @@ class Handler(http.server.BaseHTTPRequestHandler): self.end_headers() last_resp_id = None last_output = None + last_status = None for event in stream_fn(upstream): self.wfile.write(event.encode("utf-8")) self.wfile.flush() @@ -897,13 +981,16 @@ class Handler(http.server.BaseHTTPRequestHandler): if d.get("type") == "response.completed": last_resp_id = d.get("response", {}).get("id") last_output = d.get("response", {}).get("output", []) + last_status = d.get("response", {}).get("status") except: pass + _log_resp(last_resp_id, last_status, last_output) if last_resp_id and input_data is not None: store_response(last_resp_id, input_data, last_output) else: result = nonstream_fn(upstream) self.send_json(200, result) rid = result.get("id") + _log_resp(rid, result.get("status"), result.get("output", [])) if rid and input_data is not None: store_response(rid, input_data, result.get("output", []))