"""
Klynx Agent - Tool Registry
,
"""

import os
import re
import json
import hashlib
import shutil
import subprocess
import inspect
from typing import Optional, Tuple, List, Dict, Any, get_origin, get_args, Union
from pathlib import Path


class ToolRegistry:
    ""","""
    
    #  working_dir
    working_dir = "."
    virtual_root: Optional[Path] = None
    allow_shell_commands: bool = True
    
    @classmethod
    def set_working_dir(cls, working_dir: str):
        """"""
        cls.working_dir = os.path.abspath(working_dir)
        cls.virtual_root = Path(cls.working_dir).resolve()

    @classmethod
    def configure_security(
        cls,
        virtual_root: Optional[str] = None,
        allow_shell_commands: Optional[bool] = None,
    ) -> None:
        """."""
        if virtual_root is not None:
            root = str(virtual_root).strip()
            if root:
                cls.virtual_root = Path(root).expanduser().resolve()
            else:
                cls.virtual_root = None
        if allow_shell_commands is not None:
            cls.allow_shell_commands = bool(allow_shell_commands)

    @classmethod
    def _normalize_tool_params(cls, tool_name: str, func: Any, params: Dict[str, Any]) -> Dict[str, Any]:
        call_params = dict(params or {})
        if tool_name == "execute_command":
            if "command" not in call_params and call_params.get("cmd"):
                call_params["command"] = call_params.get("cmd")
            if "cwd" not in call_params and call_params.get("workdir"):
                call_params["cwd"] = call_params.get("workdir")
            if "timeout" not in call_params and call_params.get("timeout_ms") is not None:
                call_params["timeout_ms"] = call_params.get("timeout_ms")

        try:
            signature = inspect.signature(func)
            accepted = set(signature.parameters.keys())
            return {
                key: value
                for key, value in call_params.items()
                if key in accepted
            }
        except (TypeError, ValueError):
            return call_params

    @staticmethod
    def _is_within_root(path: Path, root: Path) -> bool:
        try:
            return os.path.commonpath(
                [os.path.normcase(str(path)), os.path.normcase(str(root))]
            ) == os.path.normcase(str(root))
        except Exception:
            return False
    
    @classmethod
    def _resolve_path(cls, path: str) -> Path:
        ""","""
        file_path = Path(path)
        if not file_path.is_absolute():
            file_path = Path(cls.working_dir) / file_path
        resolved = file_path.resolve()
        root = cls.virtual_root
        if root is not None and not cls._is_within_root(resolved, root):
            raise ValueError(f"path escapes virtual root: {resolved} (root={root})")
        return resolved
    @classmethod
    def read_file(
        cls,
        path: str,
        start_line: Optional[int] = None,
        end_line: Optional[int] = None,
        offset: Optional[int] = None,
        limit: Optional[int] = None,
        mode: Optional[str] = None,
        reason: Optional[str] = None,
        hit_id: Optional[str] = None,
    ) -> str:
        """
        , slice/indentation .
        """
        try:
            file_path = cls._resolve_path(path)
            if not file_path.exists():
                return f"<error>: {path}</error>"

            if not file_path.is_file():
                return f"<error>: {path}</error>"

            with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
                lines = f.readlines()

            total_lines = len(lines)
            use_offset_mode = offset is not None or limit is not None
            mode_norm = str(mode or "slice").strip().lower()
            if mode_norm not in {"slice", "indentation"}:
                mode_norm = "slice"
            reason_text = str(reason or "").strip()
            hit_text = str(hit_id or "").strip()

            def _indent_width(line: str) -> int:
                expanded = line.replace("\t", " " * 4)
                return len(expanded) - len(expanded.lstrip(" "))

            if total_lines <= 0:
                start = 0
                end = 0
                start_line_no = 0
                end_line_no = 0
                effective_limit = 0 if limit is None else max(0, int(limit or 0))
                anchor_line_no = 0
            elif mode_norm == "indentation":
                try:
                    anchor_line_no = int(start_line or 0)
                except Exception:
                    anchor_line_no = 0
                if anchor_line_no <= 0:
                    try:
                        anchor_line_no = int(offset or 0) + 1
                    except Exception:
                        anchor_line_no = 1
                anchor_line_no = max(1, min(total_lines, anchor_line_no))
                anchor_idx = anchor_line_no - 1

                try:
                    effective_limit = int(limit or 120)
                except Exception:
                    effective_limit = 120
                effective_limit = max(1, min(effective_limit, 800))

                anchor_raw = lines[anchor_idx].rstrip("\n").rstrip("\r")
                anchor_indent = _indent_width(anchor_raw) if anchor_raw.strip() else 0

                start = anchor_idx
                while start > 0:
                    prev_raw = lines[start - 1].rstrip("\n").rstrip("\r")
                    if not prev_raw.strip():
                        start -= 1
                        continue
                    if _indent_width(prev_raw) < anchor_indent:
                        break
                    start -= 1

                block_end = anchor_idx + 1
                while block_end < total_lines:
                    cur_raw = lines[block_end].rstrip("\n").rstrip("\r")
                    if not cur_raw.strip():
                        block_end += 1
                        continue
                    if _indent_width(cur_raw) < anchor_indent:
                        break
                    block_end += 1

                end = min(total_lines, max(block_end, start + 1), start + effective_limit)
                start_line_no = start + 1
                end_line_no = end
            elif use_offset_mode:
                try:
                    start = int(offset or 0)
                except Exception:
                    start = 0
                try:
                    effective_limit = int(limit or 200)
                except Exception:
                    effective_limit = 200
                start = max(0, start)
                effective_limit = max(1, effective_limit)
                end = min(total_lines, start + effective_limit)
                start_line_no = start + 1
                end_line_no = end
                anchor_line_no = start_line_no
            else:
                try:
                    start = (int(start_line) - 1) if start_line is not None else 0
                except Exception:
                    start = 0
                start = max(0, start)
                try:
                    if end_line is not None:
                        end = int(end_line)
                    elif limit is not None:
                        end = start + int(limit)
                    else:
                        end = total_lines
                except Exception:
                    end = total_lines
                end = max(start, min(total_lines, end))
                start_line_no = start + 1
                end_line_no = end
                effective_limit = max(0, end - start)
                anchor_line_no = start_line_no

            result_lines = []
            for i in range(start, end):
                line_num = i + 1
                line_content = lines[i].rstrip("\n").rstrip("\r")
                result_lines.append(f"{line_num:4d} | {line_content}")

            has_more = end < total_lines
            next_offset = end if has_more else -1
            next_start_line = end + 1 if has_more else -1

            def _escape_attr(text: str) -> str:
                return (
                    str(text or "")
                    .replace("&", "&amp;")
                    .replace("<", "&lt;")
                    .replace(">", "&gt;")
                    .replace('"', "&quot;")
                    .replace("'", "&apos;")
                )

            abs_path = str(file_path)
            content_text = "\n".join(result_lines)
            content_hash = hashlib.sha1(content_text.encode("utf-8")).hexdigest()[:16]
            chunk_seed = f"{abs_path}:{start_line_no}:{end_line_no}:{content_hash}"
            chunk_id = hashlib.sha1(chunk_seed.encode("utf-8")).hexdigest()[:16]

            header = (
                f'<file_chunk path="{_escape_attr(abs_path)}" request_path="{_escape_attr(path)}" '
                f'total_lines="{total_lines}" start_line="{start_line_no}" end_line="{end_line_no}" '
                f'offset="{start}" limit="{effective_limit}" has_more="{str(has_more).lower()}" '
                f'next_offset="{next_offset}" next_start_line="{next_start_line}" '
                f'mode="{_escape_attr(mode_norm)}" anchor_line="{anchor_line_no}" '
                f'reason="{_escape_attr(reason_text)}" hit_id="{_escape_attr(hit_text)}" '
                f'chunk_id="{chunk_id}" content_hash="{content_hash}">'
            )
            if content_text:
                return f"{header}\n{content_text}\n</file_chunk>"
            return f"{header}\n</file_chunk>"

        except Exception as e:
            return f"<error>: {str(e)}</error>"
    @classmethod
    def write_to_file(cls, path: str, content: str) -> str:
        """
        ()
        
        Args:
            path: 
            content: 
            
        Returns:
            
        """
        try:
            file_path = cls._resolve_path(path)
            
            # 
            file_path.parent.mkdir(parents=True, exist_ok=True)
            
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(content)
            
            return f"<success>: {path}</success>"
            
        except Exception as e:
            return f"<error>: {str(e)}</error>"
    
    @classmethod
    def replace_in_file(cls, path: str, diff_string: Optional[str] = None, diff: Optional[str] = None) -> str:
        """
         SEARCH/REPLACE 
        
        Args:
            path: 
            diff_string:  SEARCH/REPLACE ()
            diff: ()
                        :
                        <<<<<<< SEARCH
                        
                        =======
                        
                        >>>>>>> REPLACE
                        
        Returns:
            
        """
        try:
            diff_payload = diff_string if diff_string is not None else diff
            if not diff_payload:
                return "<error>replace_in_file : diff_string( diff)</error>"

            file_path = cls._resolve_path(path)
            if not file_path.exists():
                return f"<error>: {path}</error>"
            
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                original_content = f.read()
            
            #  SEARCH/REPLACE 
            pattern = r'<<<<<<< SEARCH\n(.*?)=======\n(.*?)>>>>>>> REPLACE'
            matches = re.findall(pattern, diff_payload, re.DOTALL)
            
            if not matches:
                return "<error> SEARCH/REPLACE ,</error>"
            
            modified_content = original_content
            replacements_count = 0
            
            for search_block, replace_block in matches:
                # 
                search_block = search_block.rstrip('\n')
                replace_block = replace_block.rstrip('\n')
                
                if search_block in modified_content:
                    modified_content = modified_content.replace(search_block, replace_block, 1)
                    replacements_count += 1
                else:
                    return f"<error>: {search_block[:50]}...</error>"
            
            # 
            with open(file_path, 'w', encoding='utf-8') as f:
                f.write(modified_content)
            
            return f"<success> {replacements_count} </success>"
            
        except Exception as e:
            return f"<error>: {str(e)}</error>"

    @classmethod
    def _parse_apply_patch(cls, patch: str) -> Tuple[Optional[List[Dict[str, Any]]], str]:
        text = str(patch or "").replace("\r\n", "\n").replace("\r", "\n")
        lines = text.split("\n")
        while lines and lines[0] == "":
            lines.pop(0)
        while lines and lines[-1] == "":
            lines.pop()

        if not lines or lines[0] != "*** Begin Patch":
            return None, "patch  *** Begin Patch "
        if lines[-1] != "*** End Patch":
            return None, "patch  *** End Patch "

        ops: List[Dict[str, Any]] = []
        idx = 1
        end = len(lines) - 1
        update_prefixes = ("*** Update File: ", "*** File: ")
        section_prefixes = ("*** Add File: ", "*** Delete File: ", *update_prefixes)
        while idx < end:
            line = lines[idx]
            if line.startswith("*** Add File: "):
                path = line[len("*** Add File: "):].strip()
                if not path:
                    return None, "Add File "
                idx += 1
                add_lines: List[str] = []
                while idx < end and not lines[idx].startswith("*** "):
                    body_line = lines[idx]
                    if not body_line.startswith("+"):
                        return None, f" {path}  + "
                    add_lines.append(body_line[1:])
                    idx += 1
                if not add_lines:
                    return None, f" {path} "
                ops.append({"type": "add", "path": path, "lines": add_lines})
                continue

            if line.startswith("*** Delete File: "):
                path = line[len("*** Delete File: "):].strip()
                if not path:
                    return None, "Delete File "
                ops.append({"type": "delete", "path": path})
                idx += 1
                continue

            if line.startswith(update_prefixes):
                matched_prefix = next(
                    prefix for prefix in update_prefixes if line.startswith(prefix)
                )
                path = line[len(matched_prefix):].strip()
                if not path:
                    return None, "Update File "
                idx += 1
                move_to = ""
                if idx < end and lines[idx].startswith("*** Move to: "):
                    move_to = lines[idx][len("*** Move to: "):].strip()
                    if not move_to:
                        return None, f" {path}  Move to "
                    idx += 1
                change_lines: List[str] = []
                while idx < end and not lines[idx].startswith(section_prefixes):
                    change_lines.append(lines[idx])
                    idx += 1
                if not change_lines:
                    return None, f" {path} "
                ops.append({"type": "update", "path": path, "move_to": move_to, "changes": change_lines})
                continue

            return None, f" patch : {line}"

        return ops, ""

    @classmethod
    def _split_patch_hunks(cls, change_lines: List[str]) -> Tuple[Optional[List[List[Tuple[str, str]]]], str]:
        hunks: List[List[Tuple[str, str]]] = []
        current: List[Tuple[str, str]] = []

        def flush_current():
            nonlocal current
            if current:
                hunks.append(current)
                current = []

        for raw_line in change_lines:
            if raw_line == "*** End of File":
                continue
            if raw_line in {"*** Begin Update", "*** End Update"}:
                continue
            if raw_line == "@@" or raw_line.startswith("@@ "):
                flush_current()
                continue
            if not raw_line:
                return None, "patch ;"
            prefix = raw_line[0]
            if prefix not in {" ", "+", "-"}:
                return None, f": {raw_line}"
            current.append((prefix, raw_line[1:]))
        flush_current()
        if not hunks:
            return None, "patch  hunk"
        return hunks, ""

    @classmethod
    def _find_hunk_start(
        cls,
        content_lines: List[str],
        start_index: int,
        hunk: List[Tuple[str, str]],
    ) -> int:
        required = [(tag, text) for tag, text in hunk if tag != "+"]
        if not required:
            return start_index

        max_start = len(content_lines) - len(required)
        for candidate in range(max(0, start_index), max_start + 1):
            cursor = candidate
            matched = True
            for tag, text in hunk:
                if tag == "+":
                    continue
                if cursor >= len(content_lines) or content_lines[cursor] != text:
                    matched = False
                    break
                cursor += 1
            if matched:
                return candidate
        return -1

    @classmethod
    def _apply_update_hunks(
        cls,
        original_text: str,
        change_lines: List[str],
    ) -> Tuple[Optional[str], str]:
        newline = "\r\n" if "\r\n" in original_text else "\n"
        had_trailing_newline = original_text.endswith("\n") or original_text.endswith("\r")
        content_lines = original_text.replace("\r\n", "\n").replace("\r", "\n").split("\n")
        if content_lines and content_lines[-1] == "":
            content_lines = content_lines[:-1]

        hunks, error = cls._split_patch_hunks(change_lines)
        if hunks is None:
            return None, error

        search_cursor = 0
        for hunk in hunks:
            start = cls._find_hunk_start(content_lines, search_cursor, hunk)
            if start < 0:
                snippet = ""
                for tag, text in hunk:
                    if tag in {" ", "-"} and text:
                        snippet = text[:80]
                        break
                return None, f" patch hunk(, patch ): {snippet or 'missing context'}"

            old_count = sum(1 for tag, _ in hunk if tag != "+")
            replacement: List[str] = []
            source_idx = start
            for tag, text in hunk:
                if tag == " ":
                    replacement.append(content_lines[source_idx])
                    source_idx += 1
                elif tag == "-":
                    source_idx += 1
                elif tag == "+":
                    replacement.append(text)
            content_lines = content_lines[:start] + replacement + content_lines[start + old_count:]
            search_cursor = start + len(replacement)

        updated_text = newline.join(content_lines)
        if had_trailing_newline or not content_lines:
            updated_text += newline
        return updated_text, ""

    @classmethod
    def _get_staged_patch_entry(
        cls,
        staged_files: Dict[str, Dict[str, Any]],
        path: Path,
    ) -> Dict[str, Any]:
        key = str(path)
        if key in staged_files:
            return staged_files[key]

        exists = path.exists()
        is_file = path.is_file() if exists else True
        content: Optional[str] = None
        if exists and is_file:
            with open(path, "r", encoding="utf-8", errors="ignore") as f:
                content = f.read()

        entry = {
            "path": path,
            "exists": exists,
            "is_file": is_file,
            "content": content,
            "original_exists": exists,
            "original_is_file": is_file,
            "original_content": content,
        }
        staged_files[key] = entry
        return entry

    @classmethod
    def _stage_apply_patch_operations(
        cls,
        operations: List[Dict[str, Any]],
    ) -> Tuple[Optional[Dict[str, Dict[str, Any]]], int, int, int, int, str]:
        staged_files: Dict[str, Dict[str, Any]] = {}
        added = 0
        updated = 0
        deleted = 0
        moved = 0

        for op in operations:
            op_type = str(op.get("type", "") or "")
            if op_type == "add":
                raw_path = str(op.get("path", "") or "").strip()
                file_path = cls._resolve_path(raw_path)
                entry = cls._get_staged_patch_entry(staged_files, file_path)
                if entry["exists"]:
                    return None, 0, 0, 0, 0, f": {raw_path}"
                new_text = "\n".join(op.get("lines", []) or [])
                if new_text:
                    new_text += "\n"
                entry["exists"] = True
                entry["is_file"] = True
                entry["content"] = new_text
                added += 1
                continue

            if op_type == "delete":
                raw_path = str(op.get("path", "") or "").strip()
                file_path = cls._resolve_path(raw_path)
                entry = cls._get_staged_patch_entry(staged_files, file_path)
                if not entry["exists"]:
                    return None, 0, 0, 0, 0, f": {raw_path}"
                if not entry["is_file"]:
                    return None, 0, 0, 0, 0, f": {raw_path}"
                entry["exists"] = False
                entry["is_file"] = False
                entry["content"] = None
                deleted += 1
                continue

            if op_type == "update":
                raw_path = str(op.get("path", "") or "").strip()
                file_path = cls._resolve_path(raw_path)
                source_entry = cls._get_staged_patch_entry(staged_files, file_path)
                if not source_entry["exists"]:
                    return None, 0, 0, 0, 0, f": {raw_path}"
                if not source_entry["is_file"]:
                    return None, 0, 0, 0, 0, f": {raw_path}"

                updated_text, update_error = cls._apply_update_hunks(
                    original_text=str(source_entry.get("content", "") or ""),
                    change_lines=list(op.get("changes", []) or []),
                )
                if updated_text is None:
                    return None, 0, 0, 0, 0, update_error

                move_to = str(op.get("move_to", "") or "").strip()
                if move_to:
                    destination = cls._resolve_path(move_to)
                    if destination != file_path:
                        destination_entry = cls._get_staged_patch_entry(staged_files, destination)
                        if destination_entry["exists"]:
                            return None, 0, 0, 0, 0, f"Move to : {move_to}"
                        destination_entry["exists"] = True
                        destination_entry["is_file"] = True
                        destination_entry["content"] = updated_text
                        source_entry["exists"] = False
                        source_entry["is_file"] = False
                        source_entry["content"] = None
                        moved += 1
                    else:
                        source_entry["content"] = updated_text
                        source_entry["exists"] = True
                        source_entry["is_file"] = True
                else:
                    source_entry["content"] = updated_text
                    source_entry["exists"] = True
                    source_entry["is_file"] = True

                updated += 1
                continue

            return None, 0, 0, 0, 0, f" {op_type}"

        return staged_files, added, updated, deleted, moved, ""

    @classmethod
    def _commit_staged_apply_patch(
        cls,
        staged_files: Dict[str, Dict[str, Any]],
    ) -> Tuple[bool, str]:
        try:
            for entry in staged_files.values():
                if bool(entry.get("exists", False)) and bool(entry.get("is_file", False)):
                    path = entry["path"]
                    path.parent.mkdir(parents=True, exist_ok=True)
                    with open(path, "w", encoding="utf-8") as f:
                        f.write(str(entry.get("content", "") or ""))

            for entry in staged_files.values():
                if bool(entry.get("exists", False)):
                    continue
                path = entry["path"]
                if path.exists():
                    if not path.is_file():
                        raise IsADirectoryError(str(path))
                    path.unlink()
            return True, ""
        except Exception as exc:
            rollback_errors: List[str] = []
            for entry in staged_files.values():
                path = entry["path"]
                try:
                    if bool(entry.get("original_exists", False)) and bool(entry.get("original_is_file", False)):
                        path.parent.mkdir(parents=True, exist_ok=True)
                        with open(path, "w", encoding="utf-8") as f:
                            f.write(str(entry.get("original_content", "") or ""))
                    else:
                        if path.exists():
                            if path.is_file():
                                path.unlink()
                            else:
                                raise IsADirectoryError(str(path))
                except Exception as rollback_exc:
                    rollback_errors.append(f"{path}: {rollback_exc}")

            error = f" patch : {exc}"
            if rollback_errors:
                error += f";: {'; '.join(rollback_errors[:3])}"
            return False, error

    @classmethod
    def apply_patch(cls, patch: str) -> str:
        """
         patch .

        :
        - Add File
        - Delete File
        - Update File
        - Move to
        """
        try:
            operations, error = cls._parse_apply_patch(patch)
            if operations is None:
                return f"<error>apply_patch : {error}</error>"
            staged_files, added, updated, deleted, moved, stage_error = cls._stage_apply_patch_operations(operations)
            if staged_files is None:
                return f"<error>apply_patch : {stage_error}</error>"

            committed, commit_error = cls._commit_staged_apply_patch(staged_files)
            if not committed:
                return f"<error>apply_patch : {commit_error}</error>"

            return (
                "<success>"
                f"apply_patch : updated={updated}, added={added}, deleted={deleted}, moved={moved}"
                "</success>"
            )
        except Exception as e:
            return f"<error>apply_patch : {str(e)}</error>"
    
    @classmethod
    def execute_command(
        cls,
        command: str,
        timeout: int = 30,
        cwd: Optional[str] = None,
        workdir: Optional[str] = None,
        timeout_ms: Optional[int] = None,
        **_: Any,
    ) -> str:
        """
        
        
        Args:
            command: 
            timeout: ()
            cwd: 
            
        Returns:
            (stdout  stderr)
        """
        try:
            if not cls.allow_shell_commands:
                return "<error>execute_command </error>"

            #  - 
            dangerous_patterns = [
                r'rm\s+-rf\s+/',
                r'rm\s+-rf\s+~',
                r'>:\s*/dev/',
                r'dd\s+if=.*of=/dev/',
                r'\bformat\s+[a-z]:',
                r'\bdel\s+/[a-z\s]*\*',
            ]
            
            for pattern in dangerous_patterns:
                if re.search(pattern, command, re.IGNORECASE):
                    return f"<error>,: {command}</error>"
            
            if timeout_ms is not None:
                try:
                    timeout_ms_int = int(timeout_ms)
                except Exception:
                    timeout_ms_int = 0
                if timeout_ms_int > 0:
                    timeout = max(1, (timeout_ms_int + 999) // 1000)

            run_cwd = cls._resolve_path(cwd or workdir or cls.working_dir)

            # (shell=False, shell )
            if os.name == "nt":
                process_args = [
                    "powershell",
                    "-NoProfile",
                    "-ExecutionPolicy",
                    "Bypass",
                    "-Command",
                    command,
                ]
            else:
                process_args = ["/bin/bash", "-lc", command]

            result = subprocess.run(
                process_args,
                shell=False,
                capture_output=True,
                text=True,
                timeout=timeout,
                cwd=str(run_cwd),
            )
            
            output_parts = []
            
            if result.stdout:
                output_parts.append(f"[STDOUT]\n{result.stdout}")
            
            if result.stderr:
                output_parts.append(f"[STDERR]\n{result.stderr}")
            
            if result.returncode != 0:
                output_parts.append(f"[EXIT CODE] {result.returncode}")
            
            return '\n'.join(output_parts) if output_parts else "<success>()</success>"
            
        except subprocess.TimeoutExpired:
            return f"<error>( {timeout} )</error>"
        except Exception as e:
            return f"<error>: {str(e)}</error>"

    @staticmethod
    def _escape_xml_attr(text: Any) -> str:
        return (
            str(text or "")
            .replace("&", "&amp;")
            .replace("<", "&lt;")
            .replace(">", "&gt;")
            .replace('"', "&quot;")
            .replace("'", "&apos;")
        )

    @classmethod
    def _read_search_context(cls, file_path: Path, line_num: int, context_lines: int) -> str:
        if context_lines <= 0:
            return ""
        try:
            with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
                lines = f.readlines()
        except (UnicodeDecodeError, PermissionError, OSError):
            return ""

        start_ctx = max(0, line_num - 1 - context_lines)
        end_ctx = min(len(lines), line_num + context_lines)
        context_slice = []
        for idx in range(start_ctx, end_ctx):
            ctx_text = lines[idx].rstrip("\n").rstrip("\r")
            context_slice.append(f"{idx + 1}: {ctx_text}")
        return "\n".join(context_slice)

    @classmethod
    def _format_search_hits_output(
        cls,
        *,
        pattern: str,
        search_path: Path,
        file_pattern: str,
        backend: str,
        hits: List[Dict[str, Any]],
        files_searched: int,
    ) -> str:
        header = (
            f'<search_hits pattern="{cls._escape_xml_attr(pattern)}" '
            f'path="{cls._escape_xml_attr(str(search_path))}" '
            f'file_pattern="{cls._escape_xml_attr(file_pattern)}" '
            f'backend="{cls._escape_xml_attr(backend)}" '
            f'files_searched="{files_searched}" hits="{len(hits)}">'
        )
        body_lines = []
        for idx, hit in enumerate(hits, 1):
            context_attr = ""
            if hit.get("context"):
                context_attr = f' context="{cls._escape_xml_attr(str(hit.get("context", "")))}"'
            body_lines.append(
                f'  <hit id="{cls._escape_xml_attr(str(hit.get("id", "")))}" rank="{idx}" '
                f'file_path="{cls._escape_xml_attr(str(hit.get("file_path", "")))}" '
                f'rel_path="{cls._escape_xml_attr(str(hit.get("rel_path", "")))}" '
                f'line="{int(hit.get("line", 0) or 0)}" score="{int(hit.get("score", 0) or 0)}"{context_attr}>'
                f'{cls._escape_xml_attr(str(hit.get("preview", "")))}'
                f"</hit>"
            )
        xml_block = header + ("\n" + "\n".join(body_lines) if body_lines else "") + "\n</search_hits>"

        if not hits:
            return xml_block + "\n<info></info>"

        summary_lines = [f" {len(hits)} ( {files_searched} )"]
        for hit in hits[: min(len(hits), 20)]:
            summary_lines.append(
                f"[{hit['id']}] {hit['rel_path']}:{hit['line']} (score={hit['score']}): {hit['preview']}"
            )
        return xml_block + "\n" + "\n".join(summary_lines)

    @classmethod
    def _search_in_files_with_rg(
        cls,
        *,
        pattern: str,
        search_path: Path,
        file_pattern: str,
        case_insensitive: bool,
        is_regex: bool,
        max_results: int,
        context_lines: int,
    ) -> Optional[str]:
        rg_bin = shutil.which("rg")
        if not rg_bin:
            return None

        args = [rg_bin, "--json", "--line-number", "--color", "never", "--no-heading"]
        if case_insensitive:
            args.append("-i")
        if not is_regex:
            args.append("-F")
        if file_pattern and file_pattern != "*" and search_path.is_dir():
            args.extend(["--glob", file_pattern])
        args.extend([pattern, str(search_path)])

        try:
            result = subprocess.run(
                args,
                shell=False,
                capture_output=True,
                text=True,
                cwd=str(cls.working_dir),
            )
        except FileNotFoundError:
            return None
        except Exception:
            return None

        if result.returncode not in (0, 1):
            stderr = str(result.stderr or "").strip()
            if is_regex and stderr:
                return f"<error>: {stderr}</error>"
            return None

        base_for_rel = search_path if search_path.is_dir() else search_path.parent
        hits: List[Dict[str, Any]] = []
        matched_files = set()
        for raw_line in str(result.stdout or "").splitlines():
            raw_line = raw_line.strip()
            if not raw_line:
                continue
            try:
                event = json.loads(raw_line)
            except json.JSONDecodeError:
                continue
            if str(event.get("type", "") or "") != "match":
                continue
            data = event.get("data", {}) or {}
            path_data = data.get("path", {}) or {}
            path_text = str(path_data.get("text", "") or "").strip()
            if not path_text:
                continue

            resolved_path = Path(path_text)
            if not resolved_path.is_absolute():
                resolved_path = (Path(cls.working_dir) / resolved_path).resolve()
            else:
                resolved_path = resolved_path.resolve()
            abs_path = str(resolved_path)
            try:
                rel_path = str(resolved_path.relative_to(base_for_rel.resolve()))
            except Exception:
                rel_path = str(resolved_path.name)

            try:
                line_num = int(data.get("line_number", 0) or 0)
            except Exception:
                line_num = 0
            line_content = str((data.get("lines", {}) or {}).get("text", "") or "")
            line_content = line_content.rstrip("\n").rstrip("\r")
            submatches = data.get("submatches", []) or []
            match_start = None
            if submatches:
                try:
                    match_start = int((submatches[0] or {}).get("start", 0) or 0)
                except Exception:
                    match_start = None

            preview = line_content.strip()
            if len(preview) > 220:
                preview = preview[:220] + "..."

            score = 100
            if match_start == 0:
                score += 8
            score += max(0, 5 - min(5, line_num // 200))
            if file_pattern and file_pattern != "*":
                score += 2

            hit_seed = f"{abs_path}:{line_num}:{line_content[:180]}"
            hit_id = hashlib.sha1(hit_seed.encode("utf-8")).hexdigest()[:12]
            hit = {
                "id": hit_id,
                "file_path": abs_path,
                "rel_path": rel_path,
                "line": line_num,
                "score": score,
                "preview": preview,
            }
            if context_lines > 0:
                context = cls._read_search_context(resolved_path, line_num, context_lines)
                if context:
                    hit["context"] = context

            hits.append(hit)
            matched_files.add(abs_path)
            if len(hits) >= max_results:
                break

        hits.sort(key=lambda item: (-int(item.get("score", 0)), str(item.get("file_path", "")), int(item.get("line", 0))))
        if len(hits) > max_results:
            hits = hits[:max_results]

        return cls._format_search_hits_output(
            pattern=pattern,
            search_path=search_path,
            file_pattern=file_pattern,
            backend="rg",
            hits=hits,
            files_searched=len(matched_files),
        )

    @classmethod
    def _search_in_files_python(
        cls,
        *,
        pattern: str,
        search_path: Path,
        file_pattern: str,
        case_insensitive: bool,
        is_regex: bool,
        max_results: int,
        context_lines: int,
    ) -> str:
        import fnmatch

        flags = re.IGNORECASE if case_insensitive else 0
        if is_regex:
            try:
                regex = re.compile(pattern, flags)
            except re.error as e:
                return f"<error>: {e}</error>"
        else:
            regex = re.compile(re.escape(pattern), flags)

        if search_path.is_file():
            files_to_search = [search_path]
            base_for_rel = search_path.parent
        else:
            files_to_search = []
            base_for_rel = search_path
            skip_dirs = {".git", "__pycache__", "node_modules", ".venv", "venv", ".idea", ".vscode"}
            for root, dirs, files in os.walk(search_path):
                dirs[:] = [d for d in dirs if d not in skip_dirs and not d.startswith(".")]
                for filename in files:
                    if fnmatch.fnmatch(filename, file_pattern):
                        files_to_search.append(Path(root) / filename)

        files_searched = 0
        hits: List[Dict[str, Any]] = []
        for file_path in files_to_search:
            if len(hits) >= max_results:
                break
            try:
                with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
                    lines = f.readlines()
                files_searched += 1
            except (UnicodeDecodeError, PermissionError, OSError):
                continue

            abs_path = str(file_path.resolve())
            try:
                rel_path = str(file_path.resolve().relative_to(base_for_rel.resolve()))
            except Exception:
                rel_path = str(file_path.name)

            for line_num, raw_line in enumerate(lines, 1):
                if len(hits) >= max_results:
                    break
                matched = regex.search(raw_line)
                if not matched:
                    continue

                line_content = raw_line.rstrip("\n").rstrip("\r")
                line_preview = line_content.strip()
                if len(line_preview) > 220:
                    line_preview = line_preview[:220] + "..."

                score = 100
                if matched.start() == 0:
                    score += 8
                score += max(0, 5 - min(5, line_num // 200))
                if file_pattern and file_pattern != "*":
                    score += 2

                hit_seed = f"{abs_path}:{line_num}:{line_content[:180]}"
                hit_id = hashlib.sha1(hit_seed.encode("utf-8")).hexdigest()[:12]
                hit = {
                    "id": hit_id,
                    "file_path": abs_path,
                    "rel_path": rel_path,
                    "line": line_num,
                    "score": score,
                    "preview": line_preview,
                }

                if context_lines > 0:
                    start_ctx = max(0, line_num - 1 - context_lines)
                    end_ctx = min(len(lines), line_num + context_lines)
                    context_slice = []
                    for idx in range(start_ctx, end_ctx):
                        ctx_text = lines[idx].rstrip("\n").rstrip("\r")
                        context_slice.append(f"{idx + 1}: {ctx_text}")
                    hit["context"] = "\n".join(context_slice)

                hits.append(hit)

        hits.sort(key=lambda item: (-int(item.get("score", 0)), str(item.get("file_path", "")), int(item.get("line", 0))))
        if len(hits) > max_results:
            hits = hits[:max_results]

        return cls._format_search_hits_output(
            pattern=pattern,
            search_path=search_path,
            file_pattern=file_pattern,
            backend="python",
            hits=hits,
            files_searched=files_searched,
        )
    
    @classmethod
    def create_directory(cls, path: str) -> str:
        """
        ()
        
        Args:
            path: 
            
        Returns:
            
        """
        try:
            dir_path = cls._resolve_path(path)
            dir_path.mkdir(parents=True, exist_ok=True)
            return f"<success>: {path}</success>"
        except Exception as e:
            return f"<error>: {str(e)}</error>"
    
    @classmethod
    def preview_file(cls, path: str, num_lines: int = 50) -> str:
        """
        ( N )
        
        Args:
            path: 
            num_lines: (50)
            
        Returns:
            
        """
        try:
            file_path = cls._resolve_path(path)
            if not file_path.exists():
                return f"<error>: {path}</error>"
            
            if not file_path.is_file():
                return f"<error>: {path}</error>"
            
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                lines = f.readlines()
            
            total_lines = len(lines)
            preview = lines[:num_lines]
            
            result_lines = []
            for i, line in enumerate(preview, 1):
                content = line.rstrip('\n').rstrip('\r')
                result_lines.append(f"{i:4d} | {content}")
            
            result = '\n'.join(result_lines)
            
            if total_lines > num_lines:
                result += f"\n     | ... ({total_lines - num_lines} , {total_lines} )"
            
            return result
            
        except Exception as e:
            return f"<error>: {str(e)}</error>"
    
    @classmethod
    def list_directory(cls, path: str = ".", depth: int = 2) -> str:
        """
        
        
        Args:
            path: 
            depth: 
            
        Returns:
            
        """
        try:
            target_path = cls._resolve_path(path)
            if not target_path.exists():
                return f"<error>: {path}</error>"
            
            if not target_path.is_dir():
                return f"<error>: {path}</error>"
            
            lines = []
            
            def build_tree(current_path: Path, prefix: str = "", current_depth: int = 0):
                if current_depth > depth:
                    return
                
                try:
                    entries = sorted(current_path.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower()))
                except PermissionError:
                    lines.append(f"{prefix}[]")
                    return
                
                for i, entry in enumerate(entries):
                    is_last = (i == len(entries) - 1)
                    connector = "└── " if is_last else "├── "
                    
                    # 
                    if entry.name.startswith('.') and entry.name not in ['.gitignore', '.env.example']:
                        continue
                    
                    if entry.is_dir():
                        lines.append(f"{prefix}{connector}{entry.name}/")
                        if current_depth < depth:
                            extension = "    " if is_last else "│   "
                            build_tree(entry, prefix + extension, current_depth + 1)
                    else:
                        lines.append(f"{prefix}{connector}{entry.name}")
            
            lines.append(f"{target_path.name}/")
            build_tree(target_path)
            
            return '\n'.join(lines)
            
        except Exception as e:
            return f"<error>: {str(e)}</error>"
    @classmethod
    def search_in_files(
        cls,
        pattern: str,
        path: str = ".",
        file_pattern: str = "*",
        case_insensitive: bool = True,
        is_regex: bool = False,
        max_results: int = 50,
        context_lines: int = 0,
    ) -> str:
        """
        , hit , read_file .
        """
        try:
            search_path = cls._resolve_path(path)
            if not search_path.exists():
                return f"<error>: {path}</error>"
            rg_result = cls._search_in_files_with_rg(
                pattern=pattern,
                search_path=search_path,
                file_pattern=file_pattern,
                case_insensitive=case_insensitive,
                is_regex=is_regex,
                max_results=max_results,
                context_lines=context_lines,
            )
            if rg_result is not None:
                return rg_result

            return cls._search_in_files_python(
                pattern=pattern,
                search_path=search_path,
                file_pattern=file_pattern,
                case_insensitive=case_insensitive,
                is_regex=is_regex,
                max_results=max_results,
                context_lines=context_lines,
            )

        except Exception as e:
            return f"<error>: {str(e)}</error>"
    @classmethod
    def execute(cls, tool_call: dict) -> str:
        """
        
        
        Args:
            tool_call: , 'tool'  'params' 
            
        Returns:
            
        """
        tool_name = tool_call.get('tool')
        params = tool_call.get('params', {})
        
        tool_map = {
            'read_file': cls.read_file,
            'apply_patch': cls.apply_patch,
            'write_to_file': cls.write_to_file,
            'replace_in_file': cls.replace_in_file,
            'execute_command': cls.execute_command,
            'list_directory': cls.list_directory,
            'create_directory': cls.create_directory,
            'preview_file': cls.preview_file,
            'search_in_files': cls.search_in_files,
        }
        
        if tool_name not in tool_map:
            return f"<error>: {tool_name}</error>"
        
        try:
            call_params = cls._normalize_tool_params(tool_name, tool_map[tool_name], params)
            return tool_map[tool_name](**call_params)
        except Exception as e:
            return f"<error>: {str(e)}</error>"


# ======================== Native Tool Calling JSON Schemas ========================

#  OpenAI tools JSON Schema (Tool Calling, Function Calling)
#  tool_call_mode="native" , Schema  LiteLLM
TOOL_SCHEMAS = {
    "read_file": {
        "type": "function",
        "function": {
            "name": "read_file",
            "description": ". execute_command  rg, search_in_files; read_file. slice/indentation , chunk_id/content_hash.",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": ","},
                    "start_line": {"type": "integer", "description": "(1)"},
                    "end_line": {"type": "integer", "description": "()"},
                    "offset": {"type": "integer", "description": "0 ,"},
                    "limit": {"type": "integer", "description": ", 80-300"},
                    "mode": {"type": "string", "description": ":slice  indentation", "default": "slice"},
                    "reason": {"type": "string", "description": "/"},
                    "hit_id": {"type": "string", "description": " search_in_files ID"}
                },
                "required": ["path"]
            }
        }
    },
    "write_to_file": {
        "type": "function",
        "function": {
            "name": "write_to_file",
            "description": "().; apply_patch.",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": ""},
                    "content": {"type": "string", "description": ""}
                },
                "required": ["path", "content"]
            }
        }
    },
    "apply_patch": {
        "type": "function",
        "function": {
            "name": "apply_patch",
            "description": " patch .,,//.patch  *** Begin Patch / *** End Patch ; *** Update File:, *** File:.: @@ hunk  + ;@@ ,. unable to locate patch hunk /  patch hunk, patch , read_file  patch .",
            "parameters": {
                "type": "object",
                "properties": {
                    "patch": {"type": "string", "description": " patch , apply_patch "}
                },
                "required": ["patch"]
            }
        }
    },
    "replace_in_file": {
        "type": "function",
        "function": {
            "name": "replace_in_file",
            "description": ". SEARCH/REPLACE . apply_patch.",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": ""},
                    "diff_string": {"type": "string", "description": "SEARCH/REPLACE ()"},
                    "diff": {"type": "string", "description": ", diff_string"}
                },
                "required": ["path"],
                "anyOf": [
                    {"required": ["diff_string"]},
                    {"required": ["diff"]}
                ]
            }
        }
    },
    "execute_command": {
        "type": "function",
        "function": {
            "name": "execute_command",
            "description": ".,,,git ; rg --files  rg -n.",
            "parameters": {
                "type": "object",
                "properties": {
                    "command": {"type": "string", "description": ""}
                },
                "required": ["command"]
            }
        }
    },
    "list_directory": {
        "type": "function",
        "function": {
            "name": "list_directory",
            "description": ",",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": ",", "default": "."},
                    "depth": {"type": "integer", "description": ", 2", "default": 2}
                },
                "required": []
            }
        }
    },
    "create_directory": {
        "type": "function",
        "function": {
            "name": "create_directory",
            "description": "()",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": ""}
                },
                "required": ["path"]
            }
        }
    },
    "preview_file": {
        "type": "function",
        "function": {
            "name": "preview_file",
            "description": " N ",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": ""},
                    "num_lines": {"type": "integer", "description": ", 50", "default": 50}
                },
                "required": ["path"]
            }
        }
    },
    "search_in_files": {
        "type": "function",
        "function": {
            "name": "search_in_files",
            "description": ", rg, Python . hit_id/read_file ;.",
            "parameters": {
                "type": "object",
                "properties": {
                    "pattern": {"type": "string", "description": ""},
                    "path": {"type": "string", "description": ", '.'", "default": "."},
                    "file_pattern": {"type": "string", "description": ", '*.py'", "default": "*"},
                    "case_insensitive": {"type": "boolean", "description": "", "default": True},
                    "is_regex": {"type": "boolean", "description": "", "default": False},
                    "max_results": {"type": "integer", "description": "", "default": 50},
                    "context_lines": {"type": "integer", "description": "", "default": 0}
                },
                "required": ["pattern"]
            }
        }
    },
    "create_terminal": {
        "type": "function",
        "function": {
            "name": "create_terminal",
            "description": "..",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": ""}
                },
                "required": ["name"]
            }
        }
    },
    "run_in_terminal": {
        "type": "function",
        "function": {
            "name": "run_in_terminal",
            "description": "",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": ""},
                    "command": {"type": "string", "description": ""}
                },
                "required": ["name", "command"]
            }
        }
    },
    "read_terminal": {
        "type": "function",
        "function": {
            "name": "read_terminal",
            "description": "",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": ""},
                    "lines": {"type": "integer", "description": ""}
                },
                "required": ["name", "lines"]
            }
        }
    },
    "wait_terminal_until": {
        "type": "function",
        "function": {
            "name": "wait_terminal_until",
            "description": " pattern.",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": ""},
                    "op_id": {"type": "string", "description": " op_id,"},
                    "pattern": {"type": "string", "description": ","},
                    "timeout_ms": {"type": "integer", "description": "", "default": 30000},
                    "poll_interval_ms": {"type": "integer", "description": "", "default": 300}
                },
                "required": ["name"]
            }
        }
    },
    "read_terminal_since_last": {
        "type": "function",
        "function": {
            "name": "read_terminal_since_last",
            "description": ".",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": ""},
                    "op_id": {"type": "string", "description": " op_id,"},
                    "lines": {"type": "integer", "description": "", "default": 100}
                },
                "required": ["name"]
            }
        }
    },
    "run_and_wait": {
        "type": "function",
        "function": {
            "name": "run_and_wait",
            "description": " pattern.",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": ""},
                    "command": {"type": "string", "description": ""},
                    "pattern": {"type": "string", "description": ","},
                    "timeout_ms": {"type": "integer", "description": "", "default": 30000},
                    "poll_interval_ms": {"type": "integer", "description": "", "default": 300}
                },
                "required": ["name", "command"]
            }
        }
    },
    "exec_command": {
        "type": "function",
        "function": {
            "name": "exec_command",
            "description": ". REPL,shell,; session_id.",
            "parameters": {
                "type": "object",
                "properties": {
                    "cmd": {"type": "string", "description": ""},
                    "workdir": {"type": "string", "description": ","},
                    "tty": {"type": "boolean", "description": " PTY, true", "default": True},
                    "yield_time_ms": {"type": "integer", "description": "", "default": 1200},
                    "max_output_tokens": {"type": "integer", "description": " token ", "default": 1200},
                    "timeout_ms": {"type": "integer", "description": ",0 ", "default": 0},
                    "shell": {"type": "boolean", "description": " shell ", "default": True},
                    "login": {"type": "boolean", "description": " Unix shell  login shell ", "default": True}
                },
                "required": ["cmd"]
            }
        }
    },
    "write_stdin": {
        "type": "function",
        "function": {
            "name": "write_stdin",
            "description": " stdin, chars='' .",
            "parameters": {
                "type": "object",
                "properties": {
                    "session_id": {"type": "string", "description": " ID"},
                    "chars": {"type": "string", "description": ";", "default": ""},
                    "yield_time_ms": {"type": "integer", "description": "", "default": 800},
                    "max_output_tokens": {"type": "integer", "description": " token ", "default": 1200}
                },
                "required": ["session_id"]
            }
        }
    },
    "close_exec_session": {
        "type": "function",
        "function": {
            "name": "close_exec_session",
            "description": ".",
            "parameters": {
                "type": "object",
                "properties": {
                    "session_id": {"type": "string", "description": " ID"}
                },
                "required": ["session_id"]
            }
        }
    },
    "check_syntax": {
        "type": "function",
        "function": {
            "name": "check_syntax",
            "description": "",
            "parameters": {
                "type": "object",
                "properties": {
                    "path": {"type": "string", "description": ""}
                },
                "required": ["path"]
            }
        }
    },
    "open_tui": {
        "type": "function",
        "function": {
            "name": "open_tui",
            "description": " TUI ",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "TUI "},
                    "command": {"type": "string", "description": ""},
                    "rows": {"type": "integer", "description": ""},
                    "cols": {"type": "integer", "description": ""}
                },
                "required": ["name", "command"]
            }
        }
    },
    "read_tui": {
        "type": "function",
        "function": {
            "name": "read_tui",
            "description": " TUI ",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "TUI "},
                    "skip_empty_lines": {
                        "type": "boolean",
                        "description": ", true",
                        "default": True
                    }
                },
                "required": ["name"]
            }
        }
    },
    "read_tui_diff": {
        "type": "function",
        "function": {
            "name": "read_tui_diff",
            "description": " TUI .",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "TUI "}
                },
                "required": ["name"]
            }
        }
    },
    "read_tui_region": {
        "type": "function",
        "function": {
            "name": "read_tui_region",
            "description": " TUI .",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "TUI "},
                    "start_row": {"type": "integer", "description": ""},
                    "end_row": {"type": "integer", "description": ""}
                },
                "required": ["name", "start_row", "end_row"]
            }
        }
    },
    "find_text_in_tui": {
        "type": "function",
        "function": {
            "name": "find_text_in_tui",
            "description": " TUI .",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "TUI "},
                    "text": {"type": "string", "description": ""},
                    "case_insensitive": {"type": "boolean", "description": "", "default": True}
                },
                "required": ["name", "text"]
            }
        }
    },
    "send_keys": {
        "type": "function",
        "function": {
            "name": "send_keys",
            "description": " TUI ",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "TUI "},
                    "keys": {"type": "string", "description": ""}
                },
                "required": ["name", "keys"]
            }
        }
    },
    "send_keys_and_read": {
        "type": "function",
        "function": {
            "name": "send_keys_and_read",
            "description": " TUI .",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "TUI "},
                    "keys": {"type": "string", "description": ""},
                    "delay_ms": {"type": "integer", "description": "", "default": 100}
                },
                "required": ["name", "keys"]
            }
        }
    },
    "wait_tui_until": {
        "type": "function",
        "function": {
            "name": "wait_tui_until",
            "description": " TUI .",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "TUI "},
                    "text": {"type": "string", "description": ","},
                    "hash_change": {"type": "boolean", "description": "", "default": False},
                    "timeout_ms": {"type": "integer", "description": "", "default": 5000},
                    "poll_interval_ms": {"type": "integer", "description": "", "default": 200}
                },
                "required": ["name"]
            }
        }
    },
    "close_tui": {
        "type": "function",
        "function": {
            "name": "close_tui",
            "description": " TUI ",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "TUI "}
                },
                "required": ["name"]
            }
        }
    },
    "activate_tui_mode": {
        "type": "function",
        "function": {
            "name": "activate_tui_mode",
            "description": " TUI : TUI . open_tui  TUI .",
            "parameters": {
                "type": "object",
                "properties": {},
                "required": []
            }
        }
    },
    "launch_interactive_session": {
        "type": "function",
        "function": {
            "name": "launch_interactive_session",
            "description": "(): TUI .( Python REPL, Node, Vim),.",
            "parameters": {
                "type": "object",
                "properties": {
                    "command": {"type": "string", "description": ", 'python', 'vim test.py'"}
                },
                "required": ["command"]
            }
        }
    },
    "launch_interactive_session": {
        "type": "function",
        "function": {
            "name": "launch_interactive_session",
            "description": "(). exec_command(tty=true); TUI .",
            "parameters": {
                "type": "object",
                "properties": {
                    "command": {"type": "string", "description": ", 'python', 'vim test.py'"},
                    "workdir": {"type": "string", "description": ","},
                    "yield_time_ms": {"type": "integer", "description": "", "default": 1200},
                    "max_output_tokens": {"type": "integer", "description": " token ", "default": 1200},
                    "timeout_ms": {"type": "integer", "description": ",0 ", "default": 0},
                    "shell": {"type": "boolean", "description": " shell ", "default": True},
                    "login": {"type": "boolean", "description": " Unix shell  login shell ", "default": True}
                },
                "required": ["command"]
            }
        }
    },
    "web_search": {
        "type": "function",
        "function": {
            "name": "web_search",
            "description": ".(,,API,,),,. search_depth='advanced' .",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": ""},
                    "max_results": {"type": "integer", "description": "", "default": 5},
                    "search_depth": {"type": "string", "description": ": 'basic'  'advanced'", "default": "basic"}
                },
                "required": ["query"]
            }
        }
    },
    "load_skill": {
        "type": "function",
        "function": {
            "name": "load_skill",
            "description": " SKILL.md ,.",
            "parameters": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "(, skill-creator)"}
                },
                "required": ["name"]
            }
        }
    },
    "browser_open": {
        "type": "function",
        "function": {
            "name": "browser_open",
            "description": " URL..",
            "parameters": {
                "type": "object",
                "properties": {
                    "url": {"type": "string", "description": " URL"}
                },
                "required": ["url"]
            }
        }
    },
    "browser_view": {
        "type": "function",
        "function": {
            "name": "browser_view",
            "description": ". selector .",
            "parameters": {
                "type": "object",
                "properties": {
                    "selector": {"type": "string", "description": "CSS ()"}
                },
                "required": []
            }
        }
    },
    "browser_act": {
        "type": "function",
        "function": {
            "name": "browser_act",
            "description": ".action  click, type, press, hover.",
            "parameters": {
                "type": "object",
                "properties": {
                    "action": {"type": "string", "description": ": click, type, press, hover"},
                    "selector": {"type": "string", "description": "CSS "},
                    "value": {"type": "string", "description": "(type )"}
                },
                "required": ["action", "selector"]
            }
        }
    },
    "browser_scroll": {
        "type": "function",
        "function": {
            "name": "browser_scroll",
            "description": ".direction  down, up.amount  600 .",
            "parameters": {
                "type": "object",
                "properties": {
                    "direction": {"type": "string", "description": ": down  up"},
                    "amount": {"type": "integer", "description": "()"}
                },
                "required": []
            }
        }
    },
    "browser_screenshot": {
        "type": "function",
        "function": {
            "name": "browser_screenshot",
            "description": "..",
            "parameters": {
                "type": "object",
                "properties": {},
                "required": []
            }
        }
    },
    "browser_console_logs": {
        "type": "function",
        "function": {
            "name": "browser_console_logs",
            "description": "",
            "parameters": {
                "type": "object",
                "properties": {},
                "required": []
            }
        }
    },
    "attempt_completion": {
        "type": "function",
        "function": {
            "name": "attempt_completion",
            "description": ":.,.",
            "parameters": {
                "type": "object",
                "properties": {
                    "result": {"type": "string", "description": ","}
                },
                "required": ["result"]
            }
        }
    },
    "write_todos": {
        "type": "function",
        "function": {
            "name": "write_todos",
            "description": " todo ..",
            "parameters": {
                "type": "object",
                "properties": {
                    "todos": {
                        "type": "array",
                        "description": "todo . id/content/status(active_form ).",
                        "items": {
                            "type": "object",
                            "properties": {
                                "id": {"type": "string"},
                                "content": {"type": "string"},
                                "status": {
                                    "type": "string",
                                    "enum": ["pending", "in_progress", "completed"],
                                },
                                "active_form": {"type": "string"},
                            },
                            "required": ["content", "status"],
                        },
                    }
                },
                "required": ["todos"]
            }
        }
    },
    "update_task_state": {
        "type": "function",
        "function": {
            "name": "update_task_state",
            "description": ".(overall_goal)(current_task),.",
            "parameters": {
                "type": "object",
                "properties": {
                    "overall_goal": {"type": "string", "description": "(,)"},
                    "current_task": {"type": "string", "description": "()"},
                    "task_plan": {
                        "type": "array",
                        "description": ",. id/title/status.",
                        "items": {"type": "object"},
                    },
                    "current_step_id": {"type": "string", "description": "ID,"},
                    "completed_steps": {
                        "type": "array",
                        "description": "ID,",
                        "items": {"type": "string"},
                    },
                    "blocked_reason": {"type": "string", "description": ","},
                },
                "required": []
            }
        }
    },
    "read_tool_artifact": {
        "type": "function",
        "function": {
            "name": "read_tool_artifact",
            "description": ". tool_artifacts .",
            "parameters": {
                "type": "object",
                "properties": {
                    "artifact_id": {"type": "string", "description": "tool_artifact "},
                    "offset": {"type": "integer", "description": ", 0"},
                    "length": {"type": "integer", "description": ", 2000"},
                },
                "required": ["artifact_id"]
            }
        }
    },
    "run_subtask": {
        "type": "function",
        "function": {
            "name": "run_subtask",
            "description": ". actions .",
            "parameters": {
                "type": "object",
                "properties": {
                    "title": {"type": "string", "description": ""},
                    "actions": {
                        "type": "array",
                        "description": ",: {tool: string, params: object}",
                        "items": {"type": "object"},
                    },
                },
                "required": ["actions"]
            }
        }
    }
}


def _annotation_to_json_type(annotation: Any) -> str:
    """ Python  JSON Schema ."""
    if annotation is inspect._empty:
        return "string"

    origin = get_origin(annotation)
    if origin is Union:
        args = [a for a in get_args(annotation) if a is not type(None)]
        if args:
            return _annotation_to_json_type(args[0])
        return "string"

    if annotation in (str,):
        return "string"
    if annotation in (bool,):
        return "boolean"
    if annotation in (int,):
        return "integer"
    if annotation in (float,):
        return "number"
    if annotation in (list, tuple, set):
        return "array"
    if annotation in (dict,):
        return "object"

    if origin in (list, tuple, set):
        return "array"
    if origin in (dict,):
        return "object"

    return "string"


def _build_external_tool_schema(name: str, description: str, func: Any) -> dict:
    """ JSON Schema."""
    fallback_schema = {
        "type": "function",
        "function": {
            "name": name,
            "description": description if isinstance(description, str) else f": {name}",
            "parameters": {
                "type": "object",
                "properties": {
                    "input": {"type": "string", "description": ""}
                },
                "required": ["input"]
            }
        }
    }

    try:
        signature = inspect.signature(func)
    except (TypeError, ValueError):
        return fallback_schema

    properties: Dict[str, Dict[str, Any]] = {}
    required: List[str] = []

    for param_name, param in signature.parameters.items():
        if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
            continue

        json_type = _annotation_to_json_type(param.annotation)
        param_schema: Dict[str, Any] = {
            "type": json_type,
            "description": f" {param_name}",
        }
        if param.default is not inspect._empty and isinstance(param.default, (str, int, float, bool)):
            param_schema["default"] = param.default

        properties[param_name] = param_schema
        if param.default is inspect._empty:
            required.append(param_name)

    if not properties:
        return fallback_schema

    return {
        "type": "function",
        "function": {
            "name": name,
            "description": description if isinstance(description, str) else f": {name}",
            "parameters": {
                "type": "object",
                "properties": properties,
                "required": required,
            }
        }
    }


def _override_tool_schema(
    name: str,
    *,
    description: Optional[str] = None,
    property_updates: Optional[Dict[str, Dict[str, Any]]] = None,
    required: Optional[List[str]] = None,
) -> None:
    schema = TOOL_SCHEMAS.get(name)
    if not isinstance(schema, dict):
        return

    fn = schema.get("function")
    if not isinstance(fn, dict):
        return

    if isinstance(description, str) and description.strip():
        fn["description"] = description

    parameters = fn.get("parameters")
    if not isinstance(parameters, dict):
        return

    properties = parameters.setdefault("properties", {})
    if not isinstance(properties, dict):
        return

    for prop_name, prop_schema in (property_updates or {}).items():
        if not isinstance(prop_schema, dict):
            continue
        existing = properties.get(prop_name, {})
        if not isinstance(existing, dict):
            existing = {}
        merged = dict(existing)
        merged.update(prop_schema)
        properties[prop_name] = merged

    if required is not None:
        parameters["required"] = list(required)


_override_tool_schema(
    "read_file",
    description=(
        "Read a focused file slice after you narrow the target. Do not use this as a whole-repo reading tool."
    ),
)
_override_tool_schema(
    "apply_patch",
    description=(
        "Apply a structured minimal patch once you have a direct edit target. Matching uses exact context anchors: "
        "after each @@ block, every non-+ line must match the current file text exactly. The @@ line numbers are "
        "separators only and are not used for positioning. If you see unable to locate patch hunk, read_file the exact slice and retry a smaller patch."
    ),
)
_override_tool_schema(
    "execute_command",
    description=(
        "Execute a short-lived command for search, build, tests, git inspection, or one-shot validation. "
        "Do not use for REPLs or long-running foreground processes. On Windows PowerShell, prefer workdir or cwd instead of cd /d."
    ),
    property_updates={
        "cwd": {
            "type": "string",
            "description": "Optional working directory for the command.",
        },
        "workdir": {
            "type": "string",
            "description": "Alias of cwd. Prefer passing workdir instead of embedding cd commands.",
        },
        "timeout_ms": {
            "type": "integer",
            "description": "Optional timeout in milliseconds for the command.",
            "default": 30000,
        },
    },
    required=["command"],
)
_override_tool_schema(
    "search_in_files",
    description=(
        "Structured helper search for hit_id -> read_file handoff. Prefer execute_command with rg first."
    ),
)
_override_tool_schema(
    "read_terminal",
    description=(
        "Read output from a legacy terminal created by create_terminal. Do not pass exec_xxx session_id values."
    ),
)
_override_tool_schema(
    "wait_terminal_until",
    description=(
        "Wait on a legacy terminal from create_terminal until a command exits or output matches a pattern. Do not pass exec_xxx session_id values."
    ),
)
_override_tool_schema(
    "read_terminal_since_last",
    description=(
        "Read incremental output from a legacy terminal created by create_terminal, not exec_command."
    ),
)
_override_tool_schema(
    "run_and_wait",
    description=(
        "Run a command in a legacy terminal and wait for completion or pattern match. Use terminal names from create_terminal, not exec_xxx session_id values."
    ),
)
_override_tool_schema(
    "exec_command",
    description=(
        "Start an interactive terminal session for REPLs, shells, or long-running foreground programs. Returns an exec_xxx session_id; continue with write_stdin or close_exec_session."
    ),
)
_override_tool_schema(
    "write_stdin",
    description=(
        "Write to an interactive exec_command session or poll for more output with chars=''. session_id must come from exec_command or launch_interactive_session."
    ),
)
_override_tool_schema(
    "close_exec_session",
    description=(
        "Close an interactive exec_command session. session_id must come from exec_command or launch_interactive_session."
    ),
)
_override_tool_schema(
    "open_tui",
    description=(
        "Start a full-screen TUI app, automatically enable TUI guidance, and return a first-screen excerpt."
    ),
    property_updates={
        "rows": {
            "type": "integer",
            "description": "Terminal rows for the TUI session.",
            "default": 30,
        },
        "cols": {
            "type": "integer",
            "description": "Terminal columns for the TUI session.",
            "default": 100,
        },
    },
)
_override_tool_schema(
    "read_tui",
    description=(
        "Read a TUI screen snapshot. When <tui_views> exists, use it as the primary TUI evidence."
    ),
)
_override_tool_schema(
    "read_tui_diff",
    description=(
        "Read the semantic diff since the last TUI observation, with concise before/after evidence."
    ),
)
_override_tool_schema(
    "read_tui_region",
    description=(
        "Read a focused row range from the current TUI screen instead of the full viewport."
    ),
)
_override_tool_schema(
    "find_text_in_tui",
    description=(
        "Find anchor text in the current TUI screen and return row-level matches for <tui_views>."
    ),
)
_override_tool_schema(
    "send_keys",
    description=(
        "Send keys to the TUI and return a short post-action excerpt when available."
    ),
)
_override_tool_schema(
    "send_keys_and_read",
    description=(
        "Send keys to the TUI and immediately return changed rows plus an after excerpt."
    ),
)
_override_tool_schema(
    "wait_tui_until",
    description=(
        "Wait until TUI text appears or the screen hash changes, and return changed-row evidence or an excerpt on success."
    ),
)
_override_tool_schema(
    "activate_tui_mode",
    description=(
        "Explicitly enable the TUI interaction guide. open_tui also enables the guide automatically."
    ),
)
_override_tool_schema(
    "launch_interactive_session",
    description=(
        "Recommended shortcut for an interactive terminal session. It is equivalent to exec_command(tty=true)."
    ),
)


def get_json_schemas(tool_names: list, external_tools: dict = None, external_tool_funcs: dict = None) -> list:
    """
    , LiteLLM/OpenAI Tool Calling  tools 
    
    Args:
        tool_names: 
        external_tools:  {name: description} ()
        external_tool_funcs:  {name: callable} ()
        
    Returns:
        OpenAI tools 
    """
    schemas = []
    for name in tool_names:
        if name in TOOL_SCHEMAS:
            schemas.append(TOOL_SCHEMAS[name])
    
    # ( callable) schema
    if external_tools:
        for name, desc in external_tools.items():
            if name not in TOOL_SCHEMAS:
                func = None
                if external_tool_funcs:
                    func = external_tool_funcs.get(name)
                if func is not None:
                    schemas.append(_build_external_tool_schema(name, desc, func))
                else:
                    schemas.append({
                        "type": "function",
                        "function": {
                            "name": name,
                            "description": desc if isinstance(desc, str) else f": {name}",
                            "parameters": {
                                "type": "object",
                                "properties": {
                                    "input": {"type": "string", "description": ""}
                                },
                                "required": ["input"]
                            }
                        }
                    })
    
    return schemas



class XMLParser:
    """XML """
    
    @staticmethod
    def parse(content: str, extra_tool_names: list = None) -> List[Dict[str, Any]]:
        """
         LLM  XML 
        
        Args:
            content: LLM 
            extra_tool_names: ()
            
        Returns:
            
        """
        tool_calls = []
        
        #  execute_command - ()
        # 1: <execute_command><command>...</command></execute_command> ()
        # 2: <execute_command>command</execute_command>
        seen_execute_commands = set()

        def _append_execute_command(raw_command: str):
            command = (raw_command or "").strip()
            if not command:
                return
            if command in seen_execute_commands:
                return
            seen_execute_commands.add(command)
            tool_calls.append({
                'tool': 'execute_command',
                'params': {'command': command}
            })

        pattern2 = r'<execute_command>\s*<command>(.*?)</command>\s*</execute_command>'
        matches2 = re.findall(pattern2, content, re.DOTALL)
        for match in matches2:
            _append_execute_command(match)

        # , XML 
        pattern = r'<execute_command>(.*?)</execute_command>'
        matches = re.findall(pattern, content, re.DOTALL)
        for match in matches:
            if re.search(r'<\s*command\s*>', match, re.IGNORECASE):
                continue
            _append_execute_command(match)

        seen_exec_calls = set()

        def _to_optional_int(value: Any) -> Optional[int]:
            if value is None:
                return None
            text = str(value).strip()
            if not text:
                return None
            try:
                return int(text)
            except Exception:
                return None

        def _to_optional_bool(value: Any) -> Optional[bool]:
            if value is None:
                return None
            text = str(value).strip().lower()
            if not text:
                return None
            if text in {"1", "true", "yes", "on"}:
                return True
            if text in {"0", "false", "no", "off"}:
                return False
            return None

        def _append_exec_command_call(
            *,
            cmd_value: Any,
            workdir_value: Any = None,
            tty_value: Any = None,
            yield_time_value: Any = None,
            max_output_tokens_value: Any = None,
            timeout_ms_value: Any = None,
            shell_value: Any = None,
            login_value: Any = None,
        ) -> None:
            cmd_text = str(cmd_value or "").strip()
            if not cmd_text:
                return
            params: Dict[str, Any] = {"cmd": cmd_text}
            workdir_text = str(workdir_value or "").strip()
            if workdir_text:
                params["workdir"] = workdir_text
            tty_bool = _to_optional_bool(tty_value)
            if tty_bool is not None:
                params["tty"] = tty_bool
            yield_ms = _to_optional_int(yield_time_value)
            if yield_ms is not None:
                params["yield_time_ms"] = yield_ms
            max_tokens = _to_optional_int(max_output_tokens_value)
            if max_tokens is not None:
                params["max_output_tokens"] = max_tokens
            timeout_ms = _to_optional_int(timeout_ms_value)
            if timeout_ms is not None:
                params["timeout_ms"] = timeout_ms
            shell_bool = _to_optional_bool(shell_value)
            if shell_bool is not None:
                params["shell"] = shell_bool
            login_bool = _to_optional_bool(login_value)
            if login_bool is not None:
                params["login"] = login_bool
            dedupe_key = (
                params.get("cmd"),
                params.get("workdir"),
                params.get("tty"),
                params.get("yield_time_ms"),
                params.get("max_output_tokens"),
                params.get("timeout_ms"),
                params.get("shell"),
                params.get("login"),
            )
            if dedupe_key in seen_exec_calls:
                return
            seen_exec_calls.add(dedupe_key)
            tool_calls.append({"tool": "exec_command", "params": params})

        exec_block_pattern = r'<exec_command>\s*(.*?)\s*</exec_command>'
        exec_blocks = re.findall(exec_block_pattern, content, re.DOTALL | re.IGNORECASE)
        for block in exec_blocks:
            cmd_match = re.search(r'<cmd>(.*?)</cmd>', block, re.DOTALL | re.IGNORECASE)
            if not cmd_match:
                raw_text = str(block or "").strip()
                if raw_text and "<" not in raw_text:
                    _append_exec_command_call(cmd_value=raw_text)
                continue
            workdir_match = re.search(r'<workdir>(.*?)</workdir>', block, re.DOTALL | re.IGNORECASE)
            tty_match = re.search(r'<tty>(.*?)</tty>', block, re.DOTALL | re.IGNORECASE)
            yield_match = re.search(r'<yield_time_ms>(.*?)</yield_time_ms>', block, re.DOTALL | re.IGNORECASE)
            max_tokens_match = re.search(r'<max_output_tokens>(.*?)</max_output_tokens>', block, re.DOTALL | re.IGNORECASE)
            timeout_match = re.search(r'<timeout_ms>(.*?)</timeout_ms>', block, re.DOTALL | re.IGNORECASE)
            shell_match = re.search(r'<shell>(.*?)</shell>', block, re.DOTALL | re.IGNORECASE)
            login_match = re.search(r'<login>(.*?)</login>', block, re.DOTALL | re.IGNORECASE)
            _append_exec_command_call(
                cmd_value=cmd_match.group(1),
                workdir_value=workdir_match.group(1) if workdir_match else None,
                tty_value=tty_match.group(1) if tty_match else None,
                yield_time_value=yield_match.group(1) if yield_match else None,
                max_output_tokens_value=max_tokens_match.group(1) if max_tokens_match else None,
                timeout_ms_value=timeout_match.group(1) if timeout_match else None,
                shell_value=shell_match.group(1) if shell_match else None,
                login_value=login_match.group(1) if login_match else None,
            )

        seen_write_stdin_calls = set()

        def _append_write_stdin_call(
            *,
            session_id_value: Any,
            chars_value: Any = None,
            yield_time_value: Any = None,
            max_output_tokens_value: Any = None,
        ) -> None:
            session_id_text = str(session_id_value or "").strip()
            if not session_id_text:
                return
            params: Dict[str, Any] = {"session_id": session_id_text}
            if chars_value is not None:
                params["chars"] = str(chars_value)
            yield_ms = _to_optional_int(yield_time_value)
            if yield_ms is not None:
                params["yield_time_ms"] = yield_ms
            max_tokens = _to_optional_int(max_output_tokens_value)
            if max_tokens is not None:
                params["max_output_tokens"] = max_tokens
            dedupe_key = (
                params.get("session_id"),
                params.get("chars", ""),
                params.get("yield_time_ms"),
                params.get("max_output_tokens"),
            )
            if dedupe_key in seen_write_stdin_calls:
                return
            seen_write_stdin_calls.add(dedupe_key)
            tool_calls.append({"tool": "write_stdin", "params": params})

        write_stdin_pattern = r'<write_stdin>\s*(.*?)\s*</write_stdin>'
        write_stdin_blocks = re.findall(write_stdin_pattern, content, re.DOTALL | re.IGNORECASE)
        for block in write_stdin_blocks:
            session_id_match = re.search(r'<session_id>(.*?)</session_id>', block, re.DOTALL | re.IGNORECASE)
            if not session_id_match:
                continue
            chars_match = re.search(r'<chars>(.*?)</chars>', block, re.DOTALL | re.IGNORECASE)
            yield_match = re.search(r'<yield_time_ms>(.*?)</yield_time_ms>', block, re.DOTALL | re.IGNORECASE)
            max_tokens_match = re.search(r'<max_output_tokens>(.*?)</max_output_tokens>', block, re.DOTALL | re.IGNORECASE)
            _append_write_stdin_call(
                session_id_value=session_id_match.group(1),
                chars_value=chars_match.group(1) if chars_match else None,
                yield_time_value=yield_match.group(1) if yield_match else None,
                max_output_tokens_value=max_tokens_match.group(1) if max_tokens_match else None,
            )

        seen_close_exec_calls = set()
        close_exec_pattern = r'<close_exec_session>\s*(.*?)\s*</close_exec_session>'
        close_exec_blocks = re.findall(close_exec_pattern, content, re.DOTALL | re.IGNORECASE)
        for block in close_exec_blocks:
            session_id_match = re.search(r'<session_id>(.*?)</session_id>', block, re.DOTALL | re.IGNORECASE)
            if not session_id_match:
                continue
            session_id_text = str(session_id_match.group(1) or "").strip()
            if not session_id_text or session_id_text in seen_close_exec_calls:
                continue
            seen_close_exec_calls.add(session_id_text)
            tool_calls.append({"tool": "close_exec_session", "params": {"session_id": session_id_text}})
        
        #  read_file -  start_line/end_line  offset/limit,
        seen_read_file_calls = set()

        def _append_read_file_call(
            path_value: str,
            start_line_value: Any = None,
            end_line_value: Any = None,
            offset_value: Any = None,
            limit_value: Any = None,
            lines_value: str = "",
            mode_value: Any = None,
            reason_value: Any = None,
            hit_id_value: Any = None,
        ) -> None:
            path_norm = str(path_value or "").strip()
            if not path_norm:
                return
            params: Dict[str, Any] = {"path": path_norm}

            def _to_int(val: Any):
                if val is None or str(val).strip() == "":
                    return None
                try:
                    return int(str(val).strip())
                except Exception:
                    return None

            start_i = _to_int(start_line_value)
            end_i = _to_int(end_line_value)
            offset_i = _to_int(offset_value)
            limit_i = _to_int(limit_value)

            lines_text = str(lines_value or "").strip()
            if lines_text:
                line_match = re.match(r"^\s*(-?\d+)\s*(?:-\s*(-?\d+)\s*)?$", lines_text)
                if line_match:
                    start_i = _to_int(line_match.group(1))
                    if line_match.group(2) is not None:
                        end_i = _to_int(line_match.group(2))
                    elif start_i is not None:
                        end_i = start_i

            if offset_i is not None:
                params["offset"] = offset_i
            if limit_i is not None:
                params["limit"] = limit_i
            if start_i is not None:
                params["start_line"] = start_i
            if end_i is not None:
                params["end_line"] = end_i
            mode_text = str(mode_value or "").strip().lower()
            if mode_text in {"slice", "indentation"}:
                params["mode"] = mode_text
            reason_text = str(reason_value or "").strip()
            if reason_text:
                params["reason"] = reason_text
            hit_text = str(hit_id_value or "").strip()
            if hit_text:
                params["hit_id"] = hit_text

            dedupe_key = (
                params.get("path", ""),
                params.get("start_line", None),
                params.get("end_line", None),
                params.get("offset", None),
                params.get("limit", None),
                params.get("mode", "slice"),
            )
            if dedupe_key in seen_read_file_calls:
                return
            seen_read_file_calls.add(dedupe_key)
            tool_calls.append({"tool": "read_file", "params": params})

        # 1: ()
        pattern_attr = r'<read_file\s+([^>]*)>\s*</read_file>'
        matches_attr = re.findall(pattern_attr, content, re.DOTALL | re.IGNORECASE)
        for attrs in matches_attr:
            path_match = re.search(r'path="([^"]+)"', attrs, re.IGNORECASE)
            if not path_match:
                continue
            start_match = re.search(r'start_line="(-?\d+)"', attrs, re.IGNORECASE)
            end_match = re.search(r'end_line="(-?\d+)"', attrs, re.IGNORECASE)
            offset_match = re.search(r'offset="(-?\d+)"', attrs, re.IGNORECASE)
            limit_match = re.search(r'limit="(-?\d+)"', attrs, re.IGNORECASE)
            mode_match = re.search(r'mode="([^"]+)"', attrs, re.IGNORECASE)
            reason_match = re.search(r'reason="([^"]+)"', attrs, re.IGNORECASE)
            hit_id_match = re.search(r'hit_id="([^"]+)"', attrs, re.IGNORECASE)
            _append_read_file_call(
                path_value=path_match.group(1),
                start_line_value=start_match.group(1) if start_match else None,
                end_line_value=end_match.group(1) if end_match else None,
                offset_value=offset_match.group(1) if offset_match else None,
                limit_value=limit_match.group(1) if limit_match else None,
                mode_value=mode_match.group(1) if mode_match else None,
                reason_value=reason_match.group(1) if reason_match else None,
                hit_id_value=hit_id_match.group(1) if hit_id_match else None,
            )

        # 2: 
        pattern_block = r'<read_file>\s*(.*?)\s*</read_file>'
        matches_block = re.findall(pattern_block, content, re.DOTALL | re.IGNORECASE)
        for block in matches_block:
            path_match = re.search(r'<path>(.*?)</path>', block, re.DOTALL | re.IGNORECASE)
            if not path_match:
                continue
            start_match = re.search(r'<start_line>(-?\d+)</start_line>', block, re.IGNORECASE)
            end_match = re.search(r'<end_line>(-?\d+)</end_line>', block, re.IGNORECASE)
            offset_match = re.search(r'<offset>(-?\d+)</offset>', block, re.IGNORECASE)
            limit_match = re.search(r'<limit>(-?\d+)</limit>', block, re.IGNORECASE)
            lines_match = re.search(r'<lines>(.*?)</lines>', block, re.DOTALL | re.IGNORECASE)
            mode_match = re.search(r'<mode>(.*?)</mode>', block, re.DOTALL | re.IGNORECASE)
            reason_match = re.search(r'<reason>(.*?)</reason>', block, re.DOTALL | re.IGNORECASE)
            hit_id_match = re.search(r'<hit_id>(.*?)</hit_id>', block, re.DOTALL | re.IGNORECASE)
            _append_read_file_call(
                path_value=path_match.group(1),
                start_line_value=start_match.group(1) if start_match else None,
                end_line_value=end_match.group(1) if end_match else None,
                offset_value=offset_match.group(1) if offset_match else None,
                limit_value=limit_match.group(1) if limit_match else None,
                lines_value=lines_match.group(1) if lines_match else "",
                mode_value=mode_match.group(1) if mode_match else None,
                reason_value=reason_match.group(1) if reason_match else None,
                hit_id_value=hit_id_match.group(1) if hit_id_match else None,
            )
        

        #  apply_patch
        seen_apply_patch_calls = set()

        def _append_apply_patch(patch_text: str):
            normalized_patch = str(patch_text or "").strip()
            if not normalized_patch:
                return
            if normalized_patch in seen_apply_patch_calls:
                return
            seen_apply_patch_calls.add(normalized_patch)
            tool_calls.append({
                'tool': 'apply_patch',
                'params': {'patch': normalized_patch}
            })

        pattern_apply_patch_param = r'<apply_patch>\s*<patch>(.*?)</patch>\s*</apply_patch>'
        matches_apply_patch_param = re.findall(pattern_apply_patch_param, content, re.DOTALL | re.IGNORECASE)
        for patch_text in matches_apply_patch_param:
            _append_apply_patch(patch_text)

        pattern_apply_patch_block = r'<apply_patch>\s*(.*?)\s*</apply_patch>'
        matches_apply_patch_block = re.findall(pattern_apply_patch_block, content, re.DOTALL | re.IGNORECASE)
        for patch_text in matches_apply_patch_block:
            if re.search(r'<\s*patch\s*>', patch_text, re.IGNORECASE):
                continue
            _append_apply_patch(patch_text)

        #  write_to_file - 
        # 1: <write_to_file path="...">content</write_to_file>
        pattern = r'<write_to_file\s+path="([^"]+)"\s*>\s*(.*?)</write_to_file>'
        matches = re.findall(pattern, content, re.DOTALL)
        for match in matches:
            path, content_text = match
            tool_calls.append({
                'tool': 'write_to_file',
                'params': {
                    'path': path,
                    'content': content_text.strip()
                }
            })
        
        # 2: <write_to_file><path>...</path><content>...</content></write_to_file> ()
        pattern2 = r'<write_to_file>\s*<path>([^<]+)</path>\s*<content>(.*?)</content>\s*</write_to_file>'
        matches2 = re.findall(pattern2, content, re.DOTALL)
        for match in matches2:
            path, content_text = match
            tool_calls.append({
                'tool': 'write_to_file',
                'params': {
                    'path': path.strip(),
                    'content': content_text.strip()
                }
            })
        
        #  replace_in_file( diff / diff_string)
        seen_replace_calls = set()

        def _append_replace_in_file(path_val: str, diff_val: str):
            path_norm = (path_val or "").strip()
            diff_norm = (diff_val or "").strip()
            if not path_norm or not diff_norm:
                return
            dedupe_key = (path_norm, diff_norm)
            if dedupe_key in seen_replace_calls:
                return
            seen_replace_calls.add(dedupe_key)
            tool_calls.append({
                'tool': 'replace_in_file',
                'params': {
                    'path': path_norm,
                    'diff_string': diff_norm
                }
            })

        pattern = r'<replace_in_file>\s*<path>([^<]+)</path>\s*<diff>\s*(.*?)</diff>\s*</replace_in_file>'
        matches = re.findall(pattern, content, re.DOTALL)
        for match in matches:
            path, diff = match
            _append_replace_in_file(path, diff)

        pattern2 = r'<replace_in_file>\s*<path>([^<]+)</path>\s*<diff_string>\s*(.*?)</diff_string>\s*</replace_in_file>'
        matches2 = re.findall(pattern2, content, re.DOTALL)
        for match in matches2:
            path, diff = match
            _append_replace_in_file(path, diff)

        pattern3 = r'<replace_in_file\s+path="([^"]+)"\s*>\s*<diff>\s*(.*?)</diff>\s*</replace_in_file>'
        matches3 = re.findall(pattern3, content, re.DOTALL)
        for match in matches3:
            path, diff = match
            _append_replace_in_file(path, diff)

        pattern4 = r'<replace_in_file\s+path="([^"]+)"\s*>\s*<diff_string>\s*(.*?)</diff_string>\s*</replace_in_file>'
        matches4 = re.findall(pattern4, content, re.DOTALL)
        for match in matches4:
            path, diff = match
            _append_replace_in_file(path, diff)
        
        #  list_directory - 
        # 1: <list_directory path="..." depth="..." recursive="..."></list_directory>
        pattern = r'<list_directory(?:\s+path="([^"]*)")?(?:\s+depth="(\d+)")?(?:\s+recursive="(true|false)")?\s*>\s*</list_directory>'
        matches = re.findall(pattern, content)
        for match in matches:
            path, depth, recursive = match
            params = {}
            if path:
                params['path'] = path
            if depth:
                params['depth'] = int(depth)
            elif recursive and recursive.lower() == 'true':
                #  recursive=true  depth, depth=10
                params['depth'] = 10
            tool_calls.append({
                'tool': 'list_directory',
                'params': params
            })
        
        # 2: <list_directory><path>...</path><recursive>...</recursive></list_directory> ()
        pattern2 = r'<list_directory>\s*<path>([^<]+)</path>(?:\s*<depth>(\d+)</depth>)?(?:\s*<recursive>(true|false)</recursive>)?\s*</list_directory>'
        matches2 = re.findall(pattern2, content)
        for match in matches2:
            path, depth, recursive = match
            params = {'path': path.strip()}
            if depth:
                params['depth'] = int(depth)
            elif recursive and recursive.lower() == 'true':
                #  recursive=true  depth, depth=10
                params['depth'] = 10
            tool_calls.append({
                'tool': 'list_directory',
                'params': params
            })
        
        #  create_directory - 
        # 1: <create_directory path="..."></create_directory>
        pattern = r'<create_directory\s+path="([^"]+)"\s*>\s*</create_directory>'
        matches = re.findall(pattern, content)
        for match in matches:
            tool_calls.append({
                'tool': 'create_directory',
                'params': {'path': match}
            })
        
        # 2: <create_directory><path>...</path></create_directory> ()
        pattern2 = r'<create_directory>\s*<path>([^<]+)</path>\s*</create_directory>'
        matches2 = re.findall(pattern2, content)
        for match in matches2:
            tool_calls.append({
                'tool': 'create_directory',
                'params': {'path': match.strip()}
            })
        
        #  preview_file - 
        # 1: <preview_file path="..." num_lines="..."></preview_file>
        pattern = r'<preview_file\s+path="([^"]+)"(?:\s+num_lines="(\d+)")?\s*>\s*</preview_file>'
        matches = re.findall(pattern, content)
        for match in matches:
            path, num_lines = match
            params = {'path': path}
            if num_lines:
                params['num_lines'] = int(num_lines)
            tool_calls.append({
                'tool': 'preview_file',
                'params': params
            })
        
        # 2: <preview_file><path>...</path></preview_file> ()
        pattern2 = r'<preview_file>\s*<path>([^<]+)</path>(?:\s*<num_lines>(\d+)</num_lines>)?\s*</preview_file>'
        matches2 = re.findall(pattern2, content)
        for match in matches2:
            path, num_lines = match
            params = {'path': path.strip()}
            if num_lines:
                params['num_lines'] = int(num_lines)
            tool_calls.append({
                'tool': 'preview_file',
                'params': params
            })
        
        #  search_in_files
        # 1: <search_in_files><pattern>...</pattern><path>...</path><file_pattern>...</file_pattern></search_in_files>
        pattern_sif = r'<search_in_files>\s*<pattern>(.*?)</pattern>(?:\s*<path>([^<]*)</path>)?(?:\s*<file_pattern>([^<]*)</file_pattern>)?(?:\s*<case_insensitive>(true|false)</case_insensitive>)?(?:\s*<is_regex>(true|false)</is_regex>)?(?:\s*<context_lines>(\d+)</context_lines>)?\s*</search_in_files>'
        matches_sif = re.findall(pattern_sif, content, re.DOTALL)
        for match in matches_sif:
            search_pattern, path, file_pat, case_ins, is_regex, ctx_lines = match
            params = {'pattern': search_pattern.strip()}
            if path:
                params['path'] = path.strip()
            if file_pat:
                params['file_pattern'] = file_pat.strip()
            if case_ins:
                params['case_insensitive'] = case_ins.lower() == 'true'
            if is_regex:
                params['is_regex'] = is_regex.lower() == 'true'
            if ctx_lines:
                params['context_lines'] = int(ctx_lines)
            tool_calls.append({
                'tool': 'search_in_files',
                'params': params
            })

        #  update_task_state
        pattern_uts = (
            r'<update_task_state>\s*'
            r'(?:<overall_goal>(.*?)</overall_goal>)?'
            r'(?:\s*<current_task>(.*?)</current_task>)?'
            r'(?:\s*<current_step_id>(.*?)</current_step_id>)?'
            r'(?:\s*<blocked_reason>(.*?)</blocked_reason>)?'
            r'(?:\s*<completed_steps>(.*?)</completed_steps>)?'
            r'(?:\s*<task_plan>(.*?)</task_plan>)?'
            r'\s*</update_task_state>'
        )
        matches_uts = re.findall(pattern_uts, content, re.DOTALL)
        for match in matches_uts:
            overall_goal, current_task, current_step_id, blocked_reason, completed_steps_text, task_plan_text = match
            params = {}
            if overall_goal and overall_goal.strip():
                params["overall_goal"] = overall_goal.strip()
            if current_task and current_task.strip():
                params["current_task"] = current_task.strip()
            if current_step_id and current_step_id.strip():
                params["current_step_id"] = current_step_id.strip()
            if blocked_reason and blocked_reason.strip():
                params["blocked_reason"] = blocked_reason.strip()
            if completed_steps_text and completed_steps_text.strip():
                steps = [
                    step.strip()
                    for step in re.split(r"[,;\n]+", completed_steps_text)
                    if step.strip()
                ]
                if steps:
                    params["completed_steps"] = steps
            if task_plan_text and task_plan_text.strip():
                try:
                    parsed_plan = json.loads(task_plan_text.strip())
                    if isinstance(parsed_plan, list):
                        params["task_plan"] = parsed_plan
                except Exception:
                    pass
            tool_calls.append({"tool": "update_task_state", "params": params})

        #  write_todos(todos  JSON)
        pattern_wt = (
            r'<write_todos>\s*'
            r'<todos>(.*?)</todos>'
            r'\s*</write_todos>'
        )
        matches_wt = re.findall(pattern_wt, content, re.DOTALL)
        for todos_text in matches_wt:
            todos: List[Dict[str, Any]] = []
            raw = (todos_text or "").strip()
            if raw:
                try:
                    parsed = json.loads(raw)
                    if isinstance(parsed, list):
                        todos = [item for item in parsed if isinstance(item, dict)]
                except Exception:
                    todos = []
            tool_calls.append({"tool": "write_todos", "params": {"todos": todos}})

        #  read_tool_artifact
        pattern_rta = (
            r'<read_tool_artifact>\s*'
            r'<artifact_id>(.*?)</artifact_id>'
            r'(?:\s*<offset>(-?\d+)</offset>)?'
            r'(?:\s*<length>(\d+)</length>)?'
            r'\s*</read_tool_artifact>'
        )
        matches_rta = re.findall(pattern_rta, content, re.DOTALL)
        for artifact_id, offset, length in matches_rta:
            params = {"artifact_id": artifact_id.strip()}
            if offset:
                params["offset"] = int(offset)
            if length:
                params["length"] = int(length)
            tool_calls.append({"tool": "read_tool_artifact", "params": params})

        #  run_subtask(actions  JSON)
        pattern_rst = (
            r'<run_subtask>\s*'
            r'(?:<title>(.*?)</title>)?'
            r'\s*<actions>(.*?)</actions>'
            r'\s*</run_subtask>'
        )
        matches_rst = re.findall(pattern_rst, content, re.DOTALL)
        for title, actions_text in matches_rst:
            params = {"title": (title or "").strip()}
            parsed_actions = []
            raw_actions = (actions_text or "").strip()
            if raw_actions:
                try:
                    candidate = json.loads(raw_actions)
                    if isinstance(candidate, list):
                        parsed_actions = candidate
                except Exception:
                    parsed_actions = []
            params["actions"] = parsed_actions
            tool_calls.append({"tool": "run_subtask", "params": params})
        
        # :()
        # : <tool_name><param1>value1</param1><param2>value2</param2></tool_name>
        if extra_tool_names:
            # 
            parsed_tools = {tc['tool'] for tc in tool_calls}
            base_tool_names = {'execute_command', 'read_file', 'write_to_file', 
                            'apply_patch', 'replace_in_file', 'list_directory', 'create_directory',
                            'preview_file', 'search_in_files', 'update_task_state', 'write_todos',
                            'read_tool_artifact', 'run_subtask'}
            for tool_name in extra_tool_names:
                if tool_name in base_tool_names or tool_name in parsed_tools:
                    continue
                #  <tool_name>...</tool_name>
                generic_pattern = rf'<{re.escape(tool_name)}>(.*?)</{re.escape(tool_name)}>'
                generic_matches = re.findall(generic_pattern, content, re.DOTALL)
                for match_body in generic_matches:
                    params = {}
                    #  <param>value</param>
                    param_pattern = r'<([a-zA-Z_][a-zA-Z0-9_]*)>(.*?)</\1>'
                    param_matches = re.findall(param_pattern, match_body, re.DOTALL)
                    for param_name, param_value in param_matches:
                        params[param_name] = param_value.strip()
                    # ,
                    if not params and match_body.strip():
                        params = {'input': match_body.strip()}
                    tool_calls.append({
                        'tool': tool_name,
                        'params': params
                    })
        
        return tool_calls
    
    @staticmethod
    def has_attempt_completion(content: str) -> bool:
        """"""
        return '<attempt_completion>' in content
