From 19e2326a6feabf63d21c36c4aa1e788fb50ffa1b Mon Sep 17 00:00:00 2001 From: pewdiepie-archdaemon Date: Mon, 29 Jun 2026 14:07:48 +0000 Subject: [PATCH] Rescue plain UI open-panel tool text --- src/tool_parsing.py | 20 ++++++++++++++++ tests/test_plain_ui_control_open_panel.py | 29 +++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 tests/test_plain_ui_control_open_panel.py diff --git a/src/tool_parsing.py b/src/tool_parsing.py index 218b53105..8682d1071 100644 --- a/src/tool_parsing.py +++ b/src/tool_parsing.py @@ -256,6 +256,17 @@ _RAW_WEB_JSON_TOOL_RE = re.compile( ) _RAW_WEB_JSON_ALLOWED_KEYS = {"query", "queries", "time_filter", "freshness", "max_pages"} +# Narrow rescue for models that ignore native tool calling and print the UI +# command as plain text. Keep this intentionally tiny: open-panel is a harmless +# frontend event, while broad plain-text parsing of shell/doc/email tools would +# be unsafe. +_PLAIN_UI_OPEN_PANEL_RE = re.compile( + r"(?im)^\s*(?:`{1,3})?\s*ui_control\s+open_panel\s+" + r"(documents?|library|gallery|images?|email|inbox|mail|sessions?|chats?|history|" + r"notes?|brain|memor(?:y|ies)|skills?|settings|preferences|cookbook|models?)" + r"\s*(?:`{1,3})?\s*$" +) + # --------------------------------------------------------------------------- # Parsing functions @@ -1018,6 +1029,14 @@ def parse_tool_blocks(text: str, skip_fenced: bool = False) -> List[ToolBlock]: if raw_web_json: blocks.append(raw_web_json[0]) + # Pattern 7: plain `ui_control open_panel notes` line. This commonly comes + # from weaker native-tool models after reading the tool docs but failing to + # emit the actual structured call. + if not blocks: + m = _PLAIN_UI_OPEN_PANEL_RE.search(text) + if m: + blocks.append(ToolBlock("ui_control", f"open_panel {m.group(1).lower()}")) + return blocks @@ -1051,6 +1070,7 @@ def strip_tool_blocks(text: str, skip_fenced: bool = False) -> str: if raw_web_json: _, (start, end) = raw_web_json cleaned = cleaned[:start] + cleaned[end:] + cleaned = _PLAIN_UI_OPEN_PANEL_RE.sub("", cleaned) # Strip bare blocks not wrapped in cleaned = _strip_bare_invoke_markup(cleaned) cleaned = re.sub(r'\n{3,}', '\n\n', cleaned) diff --git a/tests/test_plain_ui_control_open_panel.py b/tests/test_plain_ui_control_open_panel.py new file mode 100644 index 000000000..2cc2e17e2 --- /dev/null +++ b/tests/test_plain_ui_control_open_panel.py @@ -0,0 +1,29 @@ +import src.agent_tools # noqa: F401 (break agent_tools<->tool_parsing import cycle) +from src.tool_parsing import parse_tool_blocks, strip_tool_blocks + + +def test_plain_ui_control_open_panel_is_rescued_even_when_fences_skipped(): + blocks = parse_tool_blocks("ui_control open_panel notes", skip_fenced=True) + + assert len(blocks) == 1 + assert blocks[0].tool_type == "ui_control" + assert blocks[0].content == "open_panel notes" + + +def test_plain_ui_control_open_panel_rescues_backticked_line(): + blocks = parse_tool_blocks("``ui_control open_panel cookbook```", skip_fenced=True) + + assert len(blocks) == 1 + assert blocks[0].tool_type == "ui_control" + assert blocks[0].content == "open_panel cookbook" + + +def test_plain_ui_control_open_panel_strips_executed_line_only(): + text = "I'll open it now.\nui_control open_panel notes" + + assert strip_tool_blocks(text, skip_fenced=True) == "I'll open it now." + + +def test_plain_ui_control_rescue_does_not_run_other_commands(): + assert parse_tool_blocks("ui_control switch_model gemma4:31b", skip_fenced=True) == [] + assert parse_tool_blocks("bash ls", skip_fenced=True) == []