"""CLI UI 렌더링/표시 관련 유틸리티입니다. UI rendering and display utilities for the CLI. """ import json from contextlib import suppress from pathlib import Path from .config import COLORS, DEEP_AGENTS_ASCII, MAX_ARG_LENGTH, console def truncate_value(value: str, max_length: int = MAX_ARG_LENGTH) -> str: """Truncate a string value if it exceeds max_length.""" if len(value) > max_length: return value[:max_length] + "..." return value def format_tool_display(tool_name: str, tool_args: dict) -> str: # noqa: PLR0911, PLR0912, PLR0915 """Format tool calls for display with tool-specific smart formatting. Shows the most relevant information for each tool type rather than all arguments. Args: tool_name: Name of the tool being called tool_args: Dictionary of tool arguments Returns: Formatted string for display (e.g., "read_file(config.py)") Examples: read_file(path="/long/path/file.py") → "read_file(file.py)" web_search(query="how to code", max_results=5) → 'web_search("how to code")' shell(command="pip install foo") → 'shell("pip install foo")' """ def abbreviate_path(path_str: str, max_length: int = 60) -> str: """Abbreviate a file path intelligently - show basename or relative path.""" path = Path(path_str) # If it's just a filename (no directory parts), return as-is if len(path.parts) == 1: return path_str # Try to get relative path from current working directory try: cwd = Path.cwd() except OSError: cwd = None if cwd is not None: with suppress(ValueError): rel_path = path.relative_to(cwd) rel_str = str(rel_path) # Use relative if it's shorter and not too long if len(rel_str) < len(path_str) and len(rel_str) <= max_length: return rel_str # If absolute path is reasonable length, use it if len(path_str) <= max_length: return path_str # Otherwise, just show basename (filename only) return path.name # Tool-specific formatting - show the most important argument(s) if tool_name in ("read_file", "write_file", "edit_file"): # File operations: show the primary file path argument (file_path or path) path_value = tool_args.get("file_path") if path_value is None: path_value = tool_args.get("path") if path_value is not None: path = abbreviate_path(str(path_value)) return f"{tool_name}({path})" elif tool_name == "web_search": # Web search: show the query string if "query" in tool_args: query = str(tool_args["query"]) query = truncate_value(query, 100) return f'{tool_name}("{query}")' elif tool_name == "grep": # Grep: show the search pattern if "pattern" in tool_args: pattern = str(tool_args["pattern"]) pattern = truncate_value(pattern, 70) return f'{tool_name}("{pattern}")' elif tool_name == "shell": # Shell: show the command being executed if "command" in tool_args: command = str(tool_args["command"]) command = truncate_value(command, 120) return f'{tool_name}("{command}")' elif tool_name == "ls": # ls: show directory, or empty if current directory if tool_args.get("path"): path = abbreviate_path(str(tool_args["path"])) return f"{tool_name}({path})" return f"{tool_name}()" elif tool_name == "glob": # Glob: show the pattern if "pattern" in tool_args: pattern = str(tool_args["pattern"]) pattern = truncate_value(pattern, 80) return f'{tool_name}("{pattern}")' elif tool_name == "http_request": # HTTP: show method and URL parts = [] if "method" in tool_args: parts.append(str(tool_args["method"]).upper()) if "url" in tool_args: url = str(tool_args["url"]) url = truncate_value(url, 80) parts.append(url) if parts: return f"{tool_name}({' '.join(parts)})" elif tool_name == "fetch_url": # Fetch URL: show the URL being fetched if "url" in tool_args: url = str(tool_args["url"]) url = truncate_value(url, 80) return f'{tool_name}("{url}")' elif tool_name == "task": # Task: show the task description if "description" in tool_args: desc = str(tool_args["description"]) desc = truncate_value(desc, 100) return f'{tool_name}("{desc}")' elif tool_name == "write_todos": # Todos: show count of items if "todos" in tool_args and isinstance(tool_args["todos"], list): count = len(tool_args["todos"]) return f"{tool_name}({count} items)" # Fallback: generic formatting for unknown tools # Show all arguments in key=value format args_str = ", ".join(f"{k}={truncate_value(str(v), 50)}" for k, v in tool_args.items()) return f"{tool_name}({args_str})" def format_tool_message_content(content: object) -> str: """Convert ToolMessage content into a printable string.""" if content is None: return "" if isinstance(content, list): parts = [] for item in content: if isinstance(item, str): parts.append(item) else: try: parts.append(json.dumps(item)) except (TypeError, ValueError): parts.append(str(item)) return "\n".join(parts) return str(content) def show_help() -> None: """Show help information.""" console.print() console.print(DEEP_AGENTS_ASCII, style=f"bold {COLORS['primary']}") console.print() console.print("[bold]Usage:[/bold]", style=COLORS["primary"]) console.print(" deepagents [OPTIONS] Start interactive session") console.print(" deepagents list List all available agents") console.print(" deepagents reset --agent AGENT Reset agent to default prompt") console.print( " deepagents reset --agent AGENT --target SOURCE Reset agent to copy of another agent" ) console.print(" deepagents help Show this help message") console.print() console.print("[bold]Options:[/bold]", style=COLORS["primary"]) console.print(" --agent NAME Agent identifier (default: agent)") console.print( " --model MODEL Model to use (e.g., claude-sonnet-4-5-20250929, gpt-4o)" ) console.print(" --auto-approve Auto-approve tool usage without prompting") console.print( " --sandbox TYPE Remote sandbox for execution (modal, runloop, daytona)" ) console.print(" --sandbox-id ID Reuse existing sandbox (skips creation/cleanup)") console.print( " -r, --resume [ID] Resume thread: -r for most recent, -r for specific" ) console.print() console.print("[bold]Examples:[/bold]", style=COLORS["primary"]) console.print( " deepagents # Start with default agent", style=COLORS["dim"] ) console.print( " deepagents --agent mybot # Start with agent named 'mybot'", style=COLORS["dim"], ) console.print( " deepagents --model gpt-4o # Use specific model (auto-detects provider)", style=COLORS["dim"], ) console.print( " deepagents -r # Resume most recent session", style=COLORS["dim"], ) console.print( " deepagents -r abc123 # Resume specific thread", style=COLORS["dim"], ) console.print( " deepagents --auto-approve # Start with auto-approve enabled", style=COLORS["dim"], ) console.print( " deepagents --sandbox runloop # Execute code in Runloop sandbox", style=COLORS["dim"], ) console.print() console.print("[bold]Thread Management:[/bold]", style=COLORS["primary"]) console.print( " deepagents threads list # List all sessions", style=COLORS["dim"] ) console.print( " deepagents threads delete # Delete a session", style=COLORS["dim"] ) console.print() console.print("[bold]Interactive Features:[/bold]", style=COLORS["primary"]) console.print(" Enter Submit your message", style=COLORS["dim"]) console.print(" Ctrl+J Insert newline", style=COLORS["dim"]) console.print(" Shift+Tab Toggle auto-approve mode", style=COLORS["dim"]) console.print(" @filename Auto-complete files and inject content", style=COLORS["dim"]) console.print(" /command Slash commands (/help, /clear, /quit)", style=COLORS["dim"]) console.print(" !command Run bash commands directly", style=COLORS["dim"]) console.print()