fix: truncate large tool outputs to prevent Crof incomplete responses

Crof models (mimo, deepseek-v4-pro) return status=incomplete when
tool results contain too much text (e.g. full HTML pages at 8500+ tokens).
Auto-truncate tool outputs exceeding 8000 chars with truncation notice.
Combined with the 30-item conversation trim from previous commit.
This commit is contained in:
admin
2026-05-19 21:37:34 +04:00
Unverified
parent aa377024d9
commit c90912ed07
3 changed files with 21 additions and 6 deletions

View File

@@ -9,6 +9,9 @@
- Keeps system/developer messages, original user query, and most recent items - Keeps system/developer messages, original user query, and most recent items
- Drops oldest tool call/outputs from the middle when conversation grows too long - Drops oldest tool call/outputs from the middle when conversation grows too long
- Prevents `status=incomplete` errors on providers with smaller context windows - Prevents `status=incomplete` errors on providers with smaller context windows
- **Truncates large tool outputs (>8000 chars)** to prevent model output token exhaustion
- Crof's models return `incomplete` when tool results contain too much text (e.g., full HTML pages)
- Truncated outputs include `[truncated N chars]` suffix so the model knows data was cut
- Added request/response logging to `~/.cache/codex-proxy/requests.log` for debugging - Added request/response logging to `~/.cache/codex-proxy/requests.log` for debugging
- Proxy stderr no longer discarded by launcher (visible in terminal for debugging) - Proxy stderr no longer discarded by launcher (visible in terminal for debugging)

Binary file not shown.

View File

@@ -166,12 +166,24 @@ def forwarded_headers(request_headers, extra=None, browser_ua=False):
return headers return headers
_MAX_INPUT_ITEMS = 30 _MAX_INPUT_ITEMS = 30
_MAX_TOOL_OUTPUT_CHARS = 8000
def _trim_input(input_data): def _trim_input(input_data):
if not isinstance(input_data, list) or len(input_data) <= _MAX_INPUT_ITEMS: if not isinstance(input_data, list):
return input_data return input_data
out = []
for item in input_data:
if item.get("type") == "function_call_output":
o = item.get("output", "")
if len(o) > _MAX_TOOL_OUTPUT_CHARS:
item = dict(item)
item["output"] = o[:_MAX_TOOL_OUTPUT_CHARS] + f"\n... [truncated {len(o) - _MAX_TOOL_OUTPUT_CHARS} chars]"
print(f"[trim] tool output truncated {len(o)} -> {_MAX_TOOL_OUTPUT_CHARS}", file=sys.stderr)
out.append(item)
if len(out) <= _MAX_INPUT_ITEMS:
return out
head_end = 0 head_end = 0
for i, item in enumerate(input_data): for i, item in enumerate(out):
t = item.get("type") t = item.get("type")
if t == "message" and item.get("role") in ("developer", "system"): if t == "message" and item.get("role") in ("developer", "system"):
head_end = i + 1 head_end = i + 1
@@ -179,12 +191,12 @@ def _trim_input(input_data):
head_end = i + 1 head_end = i + 1
else: else:
break break
head = input_data[:head_end] head = out[:head_end]
tail_keep = _MAX_INPUT_ITEMS - len(head) tail_keep = _MAX_INPUT_ITEMS - len(head)
tail = input_data[-tail_keep:] tail = out[-tail_keep:]
trimmed = len(input_data) - len(head) - len(tail) trimmed = len(out) - len(head) - len(tail)
if trimmed > 0: if trimmed > 0:
print(f"[trim] {len(input_data)} items -> {len(head) + len(tail)} (dropped {trimmed} old items)", file=sys.stderr) print(f"[trim] {len(out)} items -> {len(head) + len(tail)} (dropped {trimmed} old items)", file=sys.stderr)
return head + tail return head + tail
# ═══════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════