fix(agent): keep the todo predicate off the model_tools/executor import path
is_todo_tool_call lived in agent/tool_executor.py and went through
canonical_tool_name, which imports model_tools. TUI resume calls it from
_todo_state_from_history on the RPC path, so the first resume in a
gateway loaded ~405 modules (2-3s) synchronously. tui_gateway/server.py
and run_agent.py also imported agent.tool_executor at module level,
adding ~142 modules to every TUI/desktop launch and breaking run_agent's
lazy-forward rule.
The predicate now lives in tools/todo_tool.py, which both startup paths
already load. It matches TODO_TOOL_NAMES ({TODO_SCHEMA name} + the legacy
aliases) and imports the bridge parser only when a tool_call entry's
args mention "todo". model_tools._LEGACY_TOOL_ALIASES derives its todo
entry from TODO_LEGACY_ALIASES, so there is one source of truth ("todo"
is the only alias mapping to todo_list). The live tool.complete path in
tool_progress uses is_todo_tool_name and the hand-kept _TODO_TOOL_NAMES
tuple is gone. The server.py noqa import is replaced by a function-local
import next to MAX_TODO_RESULT_CHARS, so a pruned name can't be swallowed
by the broad except. run_agent imports lazily. The dead TypeError arm is
dropped, and field reads use message_sanitization._tc_field.
agent/tool_executor.py is back to its pre-stack state.
Co-authored-by: JoaoMarcos44 <joaomarcosdias444@gmail.com>
This commit is contained in:
@@ -274,6 +274,49 @@ TODO_SCHEMA = {
|
||||
}
|
||||
}
|
||||
|
||||
# Pre-rename names that replay as the Todo tool. model_tools._LEGACY_TOOL_ALIASES derives its todo
|
||||
# entries from this, so the alias map and the transcript/TUI predicates below cannot drift.
|
||||
TODO_LEGACY_ALIASES = ("todo",)
|
||||
TODO_TOOL_NAMES = frozenset((TODO_SCHEMA["name"], *TODO_LEGACY_ALIASES))
|
||||
|
||||
|
||||
def is_todo_tool_name(name: Any) -> bool:
|
||||
"""True for the Todo tool's current name or a legacy alias (an already-unwrapped dispatch name)."""
|
||||
return name in TODO_TOOL_NAMES
|
||||
|
||||
|
||||
def is_todo_tool_call(tool_call: Any) -> bool:
|
||||
"""True when a transcript tool_call entry (dict or object) invoked the Todo tool.
|
||||
|
||||
Covers the current name, legacy aliases, and the ``tool_call`` bridge (``todo_list`` is deferred by
|
||||
default, and the transcript keeps the bridge name). The bridge is peeled from the recorded arguments
|
||||
only, never live tool-search config, and must wrap exactly one call. Lives here, not in
|
||||
agent.tool_executor, so TUI resume and run_agent never pull model_tools / the executor in to answer it.
|
||||
"""
|
||||
from agent.message_sanitization import _tc_field
|
||||
|
||||
fn = _tc_field(tool_call, "function")
|
||||
name, raw_args = _tc_field(fn, "name") or "", _tc_field(fn, "arguments")
|
||||
if is_todo_tool_name(name):
|
||||
return True
|
||||
# Cheap pre-check before the bridge modules load: no "todo" in the raw args means no todo inside.
|
||||
if isinstance(raw_args, str) and "todo" not in raw_args:
|
||||
return False
|
||||
from tools.tool_search_catalog import TOOL_CALL_NAME
|
||||
|
||||
if name != TOOL_CALL_NAME:
|
||||
return False
|
||||
try:
|
||||
args = json.loads(raw_args) if isinstance(raw_args, str) else raw_args
|
||||
except json.JSONDecodeError:
|
||||
return False
|
||||
if not isinstance(args, dict):
|
||||
return False
|
||||
from tools.tool_search_validation import normalize_tool_call_entries
|
||||
|
||||
entries, error = normalize_tool_call_entries(args)
|
||||
return not error and len(entries) == 1 and is_todo_tool_name(entries[0]["name"])
|
||||
|
||||
|
||||
from tools.registry import registry, tool_error
|
||||
|
||||
|
||||
Reference in New Issue
Block a user