mirror of
https://github.com/mcp-use/mcp-use.git
synced 2026-09-14 18:06:01 +08:00
266a445934
* chore: enter canary prerelease mode * Improve search tool response structure (#508) * Refactor search_tools to return metadata and results Co-authored-by: tonxipad <tonxipad@gmail.com> * docs: update API documentation * feat: enhance search_tools display and filtering capabilities - Introduced a new function to format search_tools results as a tree structure, including metadata such as total tools and namespaces. - Updated the display logic to handle both new and old formats of search_tools results for backward compatibility. - Enhanced filtering in MCPClient and CodeExecutor to include server name in search criteria, improving tool discoverability. * format * docs: update code-mode documentation to reflect changes in search_tools response structure - Modified examples to use the new response format from search_tools, which includes metadata and results. - Enhanced clarity in code snippets by adding print statements for tool discovery results. - Updated TypeScript and Python documentation to ensure consistency across both languages. * refactor: clean up whitespace in display.py for improved readability - Removed unnecessary blank lines throughout the _format_search_tools_as_tree function to enhance code clarity and maintainability. - Ensured consistent formatting in the display logic for better visual structure. * refactor: streamline line wrapping in display.py for improved readability - Consolidated parameters in the text wrapping function to enhance code clarity and maintainability. * feat: enhance search_tools metadata for improved context - Updated search_tools to return additional metadata (total_tools, namespaces, result_count) alongside results, enhancing the context for model decision-making. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: tonxipad <tonxipad@gmail.com> * chore(typescript): version packages (canary) * feat: Integrate official UI components in templates and enhance widget development experience (#517) * feat: Add ecommerce widgets and OpenAI Apps SDK UI components Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Update MCP app templates and resources - Added new fruit images to the public directory for enhanced visual content. - Removed outdated weather and ecommerce carousel components to streamline the codebase. - Updated the product search result component to utilize new UI components and improved error handling. - Modified package dependencies to include react-router and updated @openai/apps-sdk-ui version. - Enhanced the .gitignore file to include .mcp-use for better project management. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Introduce new lockfile and update dependencies - Added a new bun.lock file to manage package dependencies for the TypeScript project. - Updated pnpm-lock.yaml to include new dependencies such as @openai/apps-sdk-ui and react-router. - Enhanced the CLI tool to support aborting fetch requests and improved error handling in widget building. - Replaced outdated image assets with PNG versions for better compatibility and removed unused JPG files. - Introduced new ErrorBoundary and Image components to improve error handling and image loading in the MCP app. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Enhance Vite server configuration and update dependencies - Added a new plugin to Vite for watching the resources directory, ensuring hot module replacement (HMR) works seamlessly when widget source files change. - Updated pnpm-lock.yaml to include new dependencies such as @openai/apps-sdk-ui, react-router, and various development tools like @tailwindcss/vite and typescript. - Improved the Vite server configuration to explicitly watch files outside the root directory, enhancing the development experience. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Implement carousel component for product search results - Added a new CarouselItem component to enhance the visual presentation of fruit items in the product search results. - Introduced dynamic pointer tracking for interactive hover effects on carousel items, improving user engagement. - Updated styles for carousel items and their backgrounds to create a visually appealing blur effect. - Refactored the ProductSearchResult component to utilize the new carousel structure and improve layout responsiveness. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Update ProductSearchResult and OpenAIComponentRenderer for theme support - Added WidgetDebugger to the ProductSearchResult component for enhanced debugging capabilities. - Updated the product search result text to reflect a new template and improved context for users. - Enhanced OpenAIComponentRenderer to handle theme changes, ensuring the correct theme is applied when the iframe loads. - Modified shared-utils to include theme information in WidgetData, allowing for better theme management across components. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Refactor ProductSearchResult component and enhance WidgetFullscreenWrapper - Replaced ThemeProvider and BrowserRouter with McpUseProvider in the ProductSearchResult component for improved context management. - Removed unused getBasename function to streamline routing logic. - Updated WidgetFullscreenWrapper to support viewControls prop, allowing conditional rendering of fullscreen and picture-in-picture buttons. - Enhanced overall structure and readability of the ProductSearchResult component. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Update dependencies and enhance component functionality - Added `react-router-dom` as a dependency to support routing features. - Updated `detect-libc` to version 2.1.2 for improved compatibility. - Refactored various components for better readability and structure, including the `ProductSearchResult` and `OpenAIComponentRenderer`. - Introduced new auto-sizing functionality in `McpUseProvider` to notify OpenAI about container height changes. - Removed deprecated `WidgetDebugger` and `WidgetFullscreenWrapper` components to streamline the codebase. Co-authored-by: tonxipad <tonxipad@gmail.com> * refactor: Enhance OpenAIComponentRenderer and ToolResultDisplay for performance and readability - Memoized component to prevent unnecessary re-renders in OpenAIComponentRenderer. - Introduced new state management for iframe height notifications and improved height handling logic. - Refactored ToolResultDisplay to utilize memoization for args and result, optimizing rendering performance. - Cleaned up unused state variables and streamlined event handling for better maintainability. Co-authored-by: tonxipad <tonxipad@gmail.com> * refactor: Improve logging and code readability in various components - Enhanced logging statements in OpenAIComponentRenderer, McpUseProvider, and WidgetControls for better debugging and clarity. - Refactored memoization in ToolResultDisplay to improve performance and prevent unnecessary re-renders. - Cleaned up code formatting for improved readability across multiple components. Co-authored-by: tonxipad <tonxipad@gmail.com> * refactor: Update ProductSearchResult and shared routes for improved development experience - Modified the ProductSearchResult component to change the FAQ question and enhance the autosize feature description. - Simplified asset loading in shared routes to directly fetch from the dev server, improving development efficiency. - Enhanced security headers in shared-utils to include dev server origins for better handling of HMR scripts and resource loading. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Integrate @tanstack/react-query and enhance fruits API in apps-sdk template - Added @tanstack/react-query as a dependency to improve data fetching capabilities. - Implemented a new API endpoint in the apps-sdk template to serve a list of fruits with their respective colors. - Removed the outdated ProductSearchResult component to streamline the codebase and focus on the new API integration. Co-authored-by: tonxipad <tonxipad@gmail.com> * refactor: Clean up code formatting and improve readability in product search result components - Removed unnecessary blank lines in various TypeScript files to enhance code clarity. - Simplified function parameters in CarouselItem component for better readability. - Streamlined the Accordion and AccordionItem components by removing excess whitespace. Co-authored-by: tonxipad <tonxipad@gmail.com> * change set * chore: Remove bun.lock file to streamline dependency management - Deleted the bun.lock file to eliminate potential conflicts and simplify the dependency resolution process in the project. * feat: Update documentation and components for improved clarity and organization - Added new pages for MCP UI resources and widget components in the documentation. - Enhanced the migration guide to specify applicability for version 1.5.0 and later. - Updated the ErrorBoundary component icon for better representation. - Removed outdated template comparison section to streamline the templates documentation. * fix: Correct indentation in documentation JSON for better readability - Adjusted the indentation of the "apps-sdk-resources" entry in the documentation JSON to maintain consistent formatting. - Removed unnecessary blank lines to enhance overall clarity in the documentation structure. * chore: Update dependencies and clean up code for improved performance - Updated react-router-dom to version 7.9.5 for enhanced functionality. - Removed commented-out code in ProductSearchResult component to improve clarity. - Cleaned up logging statements in OpenAIComponentRenderer for better debugging. - Refactored height notification logic in McpUseProvider to optimize performance. - Adjusted useWidget to avoid dependency issues with URL parameters. Co-authored-by: tonxipad <tonxipad@gmail.com> * chore: Update dependencies and clean up code for improved performance - Updated react-router-dom to version 7.9.5 for enhanced functionality. - Removed commented-out code in ProductSearchResult component to improve clarity. - Cleaned up logging statements in OpenAIComponentRenderer for better debugging. - Refactored height notification logic in McpUseProvider to optimize performance. - Adjusted useWidget to avoid dependency issues with URL parameters. Co-authored-by: tonxipad <tonxipad@gmail.com> * refactor: Clean up code formatting and improve readability in OpenAIComponentRenderer and useWidget - Adjusted formatting of variable declarations for better consistency and readability. - Simplified the handleError function in OpenAIComponentRenderer for clarity. - Ensured consistent indentation in useWidget for improved code structure. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: tonxipad <tonxipad@gmail.com> * chore(typescript): version packages (canary) * feat: Add JSON-RPC message logging to Inspector tabs (MCP-536) (#519) * feat: Add ecommerce widgets and OpenAI Apps SDK UI components Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Update MCP app templates and resources - Added new fruit images to the public directory for enhanced visual content. - Removed outdated weather and ecommerce carousel components to streamline the codebase. - Updated the product search result component to utilize new UI components and improved error handling. - Modified package dependencies to include react-router and updated @openai/apps-sdk-ui version. - Enhanced the .gitignore file to include .mcp-use for better project management. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Introduce new lockfile and update dependencies - Added a new bun.lock file to manage package dependencies for the TypeScript project. - Updated pnpm-lock.yaml to include new dependencies such as @openai/apps-sdk-ui and react-router. - Enhanced the CLI tool to support aborting fetch requests and improved error handling in widget building. - Replaced outdated image assets with PNG versions for better compatibility and removed unused JPG files. - Introduced new ErrorBoundary and Image components to improve error handling and image loading in the MCP app. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Enhance Vite server configuration and update dependencies - Added a new plugin to Vite for watching the resources directory, ensuring hot module replacement (HMR) works seamlessly when widget source files change. - Updated pnpm-lock.yaml to include new dependencies such as @openai/apps-sdk-ui, react-router, and various development tools like @tailwindcss/vite and typescript. - Improved the Vite server configuration to explicitly watch files outside the root directory, enhancing the development experience. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Implement carousel component for product search results - Added a new CarouselItem component to enhance the visual presentation of fruit items in the product search results. - Introduced dynamic pointer tracking for interactive hover effects on carousel items, improving user engagement. - Updated styles for carousel items and their backgrounds to create a visually appealing blur effect. - Refactored the ProductSearchResult component to utilize the new carousel structure and improve layout responsiveness. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Update ProductSearchResult and OpenAIComponentRenderer for theme support - Added WidgetDebugger to the ProductSearchResult component for enhanced debugging capabilities. - Updated the product search result text to reflect a new template and improved context for users. - Enhanced OpenAIComponentRenderer to handle theme changes, ensuring the correct theme is applied when the iframe loads. - Modified shared-utils to include theme information in WidgetData, allowing for better theme management across components. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Refactor ProductSearchResult component and enhance WidgetFullscreenWrapper - Replaced ThemeProvider and BrowserRouter with McpUseProvider in the ProductSearchResult component for improved context management. - Removed unused getBasename function to streamline routing logic. - Updated WidgetFullscreenWrapper to support viewControls prop, allowing conditional rendering of fullscreen and picture-in-picture buttons. - Enhanced overall structure and readability of the ProductSearchResult component. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Update dependencies and enhance component functionality - Added `react-router-dom` as a dependency to support routing features. - Updated `detect-libc` to version 2.1.2 for improved compatibility. - Refactored various components for better readability and structure, including the `ProductSearchResult` and `OpenAIComponentRenderer`. - Introduced new auto-sizing functionality in `McpUseProvider` to notify OpenAI about container height changes. - Removed deprecated `WidgetDebugger` and `WidgetFullscreenWrapper` components to streamline the codebase. Co-authored-by: tonxipad <tonxipad@gmail.com> * refactor: Enhance OpenAIComponentRenderer and ToolResultDisplay for performance and readability - Memoized component to prevent unnecessary re-renders in OpenAIComponentRenderer. - Introduced new state management for iframe height notifications and improved height handling logic. - Refactored ToolResultDisplay to utilize memoization for args and result, optimizing rendering performance. - Cleaned up unused state variables and streamlined event handling for better maintainability. Co-authored-by: tonxipad <tonxipad@gmail.com> * refactor: Improve logging and code readability in various components - Enhanced logging statements in OpenAIComponentRenderer, McpUseProvider, and WidgetControls for better debugging and clarity. - Refactored memoization in ToolResultDisplay to improve performance and prevent unnecessary re-renders. - Cleaned up code formatting for improved readability across multiple components. Co-authored-by: tonxipad <tonxipad@gmail.com> * refactor: Update ProductSearchResult and shared routes for improved development experience - Modified the ProductSearchResult component to change the FAQ question and enhance the autosize feature description. - Simplified asset loading in shared routes to directly fetch from the dev server, improving development efficiency. - Enhanced security headers in shared-utils to include dev server origins for better handling of HMR scripts and resource loading. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Integrate @tanstack/react-query and enhance fruits API in apps-sdk template - Added @tanstack/react-query as a dependency to improve data fetching capabilities. - Implemented a new API endpoint in the apps-sdk template to serve a list of fruits with their respective colors. - Removed the outdated ProductSearchResult component to streamline the codebase and focus on the new API integration. Co-authored-by: tonxipad <tonxipad@gmail.com> * refactor: Clean up code formatting and improve readability in product search result components - Removed unnecessary blank lines in various TypeScript files to enhance code clarity. - Simplified function parameters in CarouselItem component for better readability. - Streamlined the Accordion and AccordionItem components by removing excess whitespace. Co-authored-by: tonxipad <tonxipad@gmail.com> * feat: Integrate JsonRpcLoggerView into PromptsTab, ResourcesTab, and ToolsTab for enhanced logging - Added JsonRpcLoggerView component to PromptsTab, ResourcesTab, and ToolsTab to facilitate RPC logging. - Adjusted ResizablePanel default sizes for better layout consistency across tabs. - Implemented keyboard shortcut handling in ToolExecutionPanel for executing tools with Cmd/Ctrl + Enter. Co-authored-by: tonxipad <tonxipad@gmail.com> * format * chore: changeset * docs: add rpc docs * chore: Remove bun.lock file and clean up pnpm-lock.yaml - Deleted the bun.lock file to streamline dependency management and eliminate potential conflicts. - Removed outdated dependencies from pnpm-lock.yaml, including entries for test_app and its associated packages, to simplify the lockfile and improve clarity. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: tonxipad <tonxipad@gmail.com> * chore(typescript): version packages (canary) * docs: update API documentation * docs: api update docs * trigger release * chore: Clean up pnpm-lock.yaml and remove bun.lock file for streamlined dependency management * chore(typescript): version packages (canary) * chore(inspector): update package.json with homepage, repository, and bugs information --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: tonxipad <tonxipad@gmail.com>
563 lines
23 KiB
Python
563 lines
23 KiB
Python
import json
|
|
import logging
|
|
import re
|
|
import textwrap
|
|
|
|
from rich.console import Console
|
|
from rich.markdown import Markdown
|
|
from rich.panel import Panel
|
|
from rich.pretty import Pretty
|
|
from rich.text import Text
|
|
|
|
console = Console()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Set code theme as a global variable
|
|
CODE_THEME = "stata-dark"
|
|
|
|
|
|
def _extract_json_from_textcontent(result):
|
|
"""Extract JSON from TextContent objects in result string."""
|
|
result_str = str(result)
|
|
|
|
# Try to extract JSON between text=' and ', annotations=
|
|
# Use greedy matching since we know the specific end pattern
|
|
pattern = r"text='(.*)',\s*annotations="
|
|
match = re.search(pattern, result_str, re.DOTALL)
|
|
|
|
if match:
|
|
json_str = match.group(1)
|
|
# Unescape the string - Python's str() escapes quotes and backslashes
|
|
json_str = json_str.replace("\\'", "'")
|
|
json_str = json_str.replace("\\\\", "\\")
|
|
try:
|
|
return json.loads(json_str)
|
|
except json.JSONDecodeError:
|
|
pass
|
|
|
|
return None
|
|
|
|
|
|
def _format_search_tools_as_tree(tools, meta=None, query=None):
|
|
"""Format search_tools result as a tree structure.
|
|
|
|
Args:
|
|
tools: List of tool dictionaries with 'server', 'name', and optionally 'description'
|
|
meta: Optional dictionary with 'total_tools', 'namespaces', 'result_count'
|
|
query: Optional query string that was used for the search
|
|
|
|
Returns:
|
|
Formatted tree string
|
|
"""
|
|
# Build meta information display
|
|
meta_lines = []
|
|
if meta:
|
|
if meta.get("total_tools") is not None:
|
|
meta_lines.append(f"Total tools: {meta['total_tools']}")
|
|
if meta.get("namespaces") and len(meta["namespaces"]) > 0:
|
|
meta_lines.append(f"Namespaces: {', '.join(meta['namespaces'])}")
|
|
if meta.get("result_count") is not None:
|
|
meta_lines.append(f"Results: {meta['result_count']}")
|
|
|
|
if not isinstance(tools, list) or len(tools) == 0:
|
|
no_results_msg = f'No tools found for query "{query}"' if query else "(no tools found)"
|
|
if meta_lines:
|
|
return "\n".join(meta_lines) + "\n\n" + no_results_msg
|
|
return no_results_msg
|
|
|
|
# Group tools by server
|
|
tools_by_server = {}
|
|
for tool in tools:
|
|
server = tool.get("server", "unknown")
|
|
if server not in tools_by_server:
|
|
tools_by_server[server] = []
|
|
tools_by_server[server].append(tool)
|
|
|
|
# Build tree structure
|
|
lines = []
|
|
|
|
# Add meta information at the top if available
|
|
if meta_lines:
|
|
lines.extend(meta_lines)
|
|
lines.append("") # Empty line before tree
|
|
|
|
servers = sorted(tools_by_server.keys())
|
|
|
|
for i, server in enumerate(servers):
|
|
server_tools = tools_by_server[server]
|
|
is_last_server = i == len(servers) - 1
|
|
server_prefix = "└─" if is_last_server else "├─"
|
|
|
|
# Use rich Text for colored output
|
|
server_text = Text(f"{server_prefix} ")
|
|
server_text.append(server, style="cyan")
|
|
server_text.append(f" ({len(server_tools)} tools)")
|
|
lines.append(server_text)
|
|
|
|
# Add tools under this server
|
|
for j, tool in enumerate(server_tools):
|
|
is_last_tool = j == len(server_tools) - 1
|
|
indent = " " if is_last_server else "│ "
|
|
tool_prefix = "└─" if is_last_tool else "├─"
|
|
|
|
# Tool name line
|
|
tool_name = tool.get("name", "unknown")
|
|
tool_text = Text(f"{indent}{tool_prefix} {tool_name}")
|
|
lines.append(tool_text)
|
|
|
|
# Description on new line, aligned with tool name
|
|
if tool.get("description"):
|
|
# Calculate indent for description lines
|
|
# Use the same base indent as the tool, then add alignment
|
|
# If not the last tool, add vertical bar to show continuation, otherwise spaces
|
|
desc_align = " " if is_last_tool else "│ "
|
|
description_indent = indent + desc_align
|
|
description = tool["description"]
|
|
|
|
# Get terminal width for wrapping (default to 120 if not available)
|
|
try:
|
|
terminal_width = console.width
|
|
except Exception:
|
|
terminal_width = 120
|
|
|
|
# Calculate available width for description
|
|
# Account for: indent + Panel padding (approximately 4 chars for borders/padding)
|
|
indent_length = len(description_indent)
|
|
available_width = max(40, terminal_width - indent_length - 4)
|
|
|
|
# Wrap description text at word boundaries
|
|
wrapped_lines = textwrap.wrap(
|
|
description, width=available_width, break_long_words=False, break_on_hyphens=False
|
|
)
|
|
|
|
# Add indent to each line (including continuation lines)
|
|
for desc_line in wrapped_lines:
|
|
desc_text = Text(description_indent)
|
|
desc_text.append(desc_line, style="dim")
|
|
lines.append(desc_text)
|
|
|
|
# Convert to string for display
|
|
result_lines = []
|
|
for line in lines:
|
|
if isinstance(line, Text):
|
|
result_lines.append(line.plain)
|
|
else:
|
|
result_lines.append(str(line))
|
|
|
|
return "\n".join(result_lines)
|
|
|
|
|
|
def _render_content(content):
|
|
"""Render content with appropriate syntax highlighting using markdown code blocks."""
|
|
if content is None:
|
|
return Markdown("```\nNone\n```", code_theme=CODE_THEME)
|
|
|
|
content_str = str(content) if not isinstance(content, str) else content
|
|
content_str_stripped = content_str.strip()
|
|
|
|
# Try to detect and render JSON
|
|
if content_str_stripped.startswith(("{", "[")):
|
|
try:
|
|
parsed = json.loads(content_str_stripped)
|
|
formatted = json.dumps(parsed, indent=2)
|
|
return Markdown(f"```json\n{formatted}\n```", code_theme=CODE_THEME)
|
|
except json.JSONDecodeError:
|
|
pass
|
|
|
|
# For dicts and lists, convert to JSON
|
|
if isinstance(content, dict | list):
|
|
try:
|
|
formatted = json.dumps(content, indent=2)
|
|
return Markdown(f"```json\n{formatted}\n```", code_theme=CODE_THEME)
|
|
except (TypeError, ValueError):
|
|
pass
|
|
|
|
# If it already has markdown code blocks, render as is
|
|
if "```" in content_str:
|
|
return Markdown(content_str, code_theme=CODE_THEME)
|
|
|
|
# Detect markdown patterns (headers, lists)
|
|
if re.search(r"^#{1,6}\s|^\*\s|^-\s", content_str_stripped, re.MULTILINE):
|
|
return Markdown(content_str, code_theme=CODE_THEME)
|
|
|
|
# Detect code patterns
|
|
if "\n" in content_str and any(kw in content_str for kw in ["def ", "class ", "import ", "function ", "const "]):
|
|
# Try to detect language
|
|
if "def " in content_str or "import " in content_str:
|
|
return Markdown(f"```python\n{content_str}\n```", code_theme=CODE_THEME)
|
|
elif "function " in content_str or "const " in content_str:
|
|
return Markdown(f"```javascript\n{content_str}\n```", code_theme=CODE_THEME)
|
|
|
|
# Default: plain text in markdown code block
|
|
return Markdown(f"```\n{content_str}\n```", code_theme=CODE_THEME)
|
|
|
|
|
|
def _pretty_print_step(item):
|
|
"""Pretty print agent step using rich formatting."""
|
|
if isinstance(item, tuple) and len(item) == 2:
|
|
action, result = item
|
|
tool_name = getattr(action, "tool", None)
|
|
tool_input = getattr(action, "tool_input", {})
|
|
|
|
console.print() # Empty line before tool
|
|
|
|
if tool_input:
|
|
if tool_name == "execute_code" and "code" in tool_input:
|
|
code = tool_input["code"]
|
|
code_md = Markdown(f"```python\n{code}\n```", code_theme=CODE_THEME)
|
|
title = f"[dim]🔧[/dim] [bold white]{tool_name}[/bold white] [dim]Input[/dim]"
|
|
console.print(Panel(code_md, title=title, border_style="dim white", padding=(0, 1)))
|
|
|
|
other_inputs = {k: v for k, v in tool_input.items() if k != "code"}
|
|
if other_inputs:
|
|
console.print(
|
|
Panel(
|
|
_render_content(other_inputs),
|
|
title="[dim]Other Parameters[/dim]",
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
else:
|
|
title = f"[dim]🔧[/dim] [bold white]{tool_name}[/bold white] [dim]Input[/dim]"
|
|
console.print(
|
|
Panel(
|
|
_render_content(tool_input),
|
|
title=title,
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
|
|
if result:
|
|
parsed_json = _extract_json_from_textcontent(result)
|
|
if parsed_json:
|
|
# Special handling for search_tools to display as tree
|
|
if tool_name == "search_tools":
|
|
# Get query from tool input
|
|
query = tool_input.get("query") if isinstance(tool_input, dict) else None
|
|
|
|
# Handle new format: object with meta and results
|
|
if isinstance(parsed_json, dict) and "results" in parsed_json:
|
|
results = parsed_json["results"]
|
|
meta = parsed_json.get("meta")
|
|
if isinstance(results, list):
|
|
tree_str = _format_search_tools_as_tree(results, meta, query)
|
|
console.print(
|
|
Panel(
|
|
Markdown(f"```\n{tree_str}\n```", code_theme=CODE_THEME),
|
|
title="[dim]Result[/dim]",
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
return
|
|
# Handle old format: direct array (backward compatibility)
|
|
elif isinstance(parsed_json, list):
|
|
query = tool_input.get("query") if isinstance(tool_input, dict) else None
|
|
tree_str = _format_search_tools_as_tree(parsed_json, None, query)
|
|
console.print(
|
|
Panel(
|
|
Markdown(f"```\n{tree_str}\n```", code_theme=CODE_THEME),
|
|
title="[dim]Result[/dim]",
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
return
|
|
|
|
if tool_name == "execute_code" and isinstance(parsed_json, dict):
|
|
execution_time = parsed_json.get("execution_time")
|
|
time_str = f" [dim]⏱️ {execution_time:.3f}s[/dim]" if execution_time is not None else ""
|
|
|
|
has_logs = "logs" in parsed_json and parsed_json["logs"]
|
|
has_result = "result" in parsed_json and parsed_json["result"] is not None
|
|
|
|
if has_result:
|
|
# Add execution time to Result only if there are no logs (logs will get the time)
|
|
result_title = f"[dim]Result[/dim]{time_str}" if not has_logs else "[dim]Result[/dim]"
|
|
console.print(
|
|
Panel(
|
|
_render_content(parsed_json["result"]),
|
|
title=result_title,
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
|
|
if has_logs:
|
|
# Add execution time to Logs panel (last panel)
|
|
console.print(
|
|
Panel(
|
|
_render_content(parsed_json["logs"]),
|
|
title=f"[dim]Logs[/dim]{time_str}",
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
|
|
if "error" in parsed_json and parsed_json["error"] is not None:
|
|
console.print(
|
|
Panel(
|
|
_render_content(parsed_json["error"]),
|
|
title="[dim red]Error[/dim red]",
|
|
border_style="red",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
else:
|
|
console.print(
|
|
Panel(
|
|
_render_content(parsed_json),
|
|
title="[dim]Result[/dim]",
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
else:
|
|
console.print(
|
|
Panel(
|
|
_render_content(result),
|
|
title="[dim]Result[/dim]",
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
else:
|
|
console.print(Pretty(item))
|
|
|
|
|
|
def _log_step(item):
|
|
"""Log agent step using logger.info with emoji messages."""
|
|
if isinstance(item, tuple) and len(item) == 2:
|
|
action, result = item
|
|
tool_name = getattr(action, "tool", None)
|
|
tool_input = getattr(action, "tool_input", {})
|
|
|
|
tool_input_str = str(tool_input)
|
|
if len(tool_input_str) > 100:
|
|
tool_input_str = tool_input_str[:97] + "..."
|
|
logger.info(f"🔧 Tool call: {tool_name} with input: {tool_input_str}")
|
|
|
|
observation_str = str(result)
|
|
if len(observation_str) > 100:
|
|
observation_str = observation_str[:97] + "..."
|
|
observation_str = observation_str.replace("\n", " ")
|
|
logger.info(f"📄 Tool result: {observation_str}")
|
|
else:
|
|
logger.info(f"Agent step: {item}")
|
|
|
|
|
|
def log_agent_step(item, pretty_print=True):
|
|
"""Display or log an agent step based on pretty_print flag."""
|
|
if pretty_print:
|
|
_pretty_print_step(item)
|
|
else:
|
|
_log_step(item)
|
|
|
|
|
|
def log_agent_stream(chunk, pretty_print=True):
|
|
"""Handle streaming events from astream_events.
|
|
|
|
Args:
|
|
chunk: Event dictionary from LangChain's astream_events
|
|
pretty_print: If True, use rich formatting. If False, do nothing.
|
|
"""
|
|
if not pretty_print:
|
|
return
|
|
|
|
event_type = chunk.get("event")
|
|
event_data = chunk.get("data", {})
|
|
name = chunk.get("name", "")
|
|
|
|
# Only process specific event types we care about
|
|
if event_type not in ("on_tool_start", "on_tool_end", "on_chat_model_stream"):
|
|
return
|
|
|
|
# Handle tool start events
|
|
if event_type == "on_tool_start":
|
|
tool_input = event_data.get("input", {})
|
|
tool_name = name.split("/")[-1] if "/" in name else name
|
|
console.print() # Empty line before tool
|
|
if tool_input:
|
|
if tool_name == "execute_code" and "code" in tool_input:
|
|
code = tool_input["code"]
|
|
code_md = Markdown(f"```python\n{code}\n```", code_theme=CODE_THEME)
|
|
title = f"[dim]🔧[/dim] [bold white]{tool_name}[/bold white] [dim]Input[/dim]"
|
|
console.print(Panel(code_md, title=title, border_style="dim white", padding=(0, 1)))
|
|
other_inputs = {k: v for k, v in tool_input.items() if k != "code"}
|
|
if other_inputs:
|
|
console.print(
|
|
Panel(
|
|
_render_content(other_inputs),
|
|
title="[dim]Other Parameters[/dim]",
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
else:
|
|
title = f"[dim]🔧[/dim] [bold white]{tool_name}[/bold white] [dim]Input[/dim]"
|
|
console.print(
|
|
Panel(
|
|
_render_content(tool_input),
|
|
title=title,
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
|
|
# Handle tool end events
|
|
elif event_type == "on_tool_end":
|
|
output = event_data.get("output", "")
|
|
|
|
if output:
|
|
# Extract content from ToolMessage if it's a ToolMessage object
|
|
output_str = None
|
|
if hasattr(output, "content"):
|
|
content = output.content
|
|
# Handle list of TextContent objects
|
|
if isinstance(content, list) and content:
|
|
# Try to extract text from TextContent objects
|
|
text_contents = []
|
|
for item in content:
|
|
if hasattr(item, "text"):
|
|
text_contents.append(item.text)
|
|
elif isinstance(item, dict) and "text" in item:
|
|
text_contents.append(item["text"])
|
|
if text_contents:
|
|
output_str = text_contents[0] if len(text_contents) == 1 else str(text_contents)
|
|
else:
|
|
output_str = str(content)
|
|
elif isinstance(content, str):
|
|
output_str = content
|
|
else:
|
|
output_str = str(content)
|
|
elif isinstance(output, dict) and "content" in output:
|
|
output_str = str(output["content"])
|
|
else:
|
|
output_str = str(output)
|
|
|
|
parsed_json = _extract_json_from_textcontent(output_str) if output_str else None
|
|
if parsed_json:
|
|
tool_name = name.split("/")[-1] if "/" in name else name
|
|
|
|
# Special handling for search_tools to display as tree
|
|
if tool_name == "search_tools":
|
|
# Try to get query from event data input
|
|
event_input = event_data.get("input", {}) if event_data else {}
|
|
query = event_input.get("query") if isinstance(event_input, dict) else None
|
|
|
|
# Handle new format: object with meta and results
|
|
if isinstance(parsed_json, dict) and "results" in parsed_json:
|
|
results = parsed_json["results"]
|
|
meta = parsed_json.get("meta")
|
|
if isinstance(results, list):
|
|
tree_str = _format_search_tools_as_tree(results, meta, query)
|
|
console.print(
|
|
Panel(
|
|
Markdown(f"```\n{tree_str}\n```", code_theme=CODE_THEME),
|
|
title="[dim]Result[/dim]",
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
return
|
|
# Handle old format: direct array (backward compatibility)
|
|
elif isinstance(parsed_json, list):
|
|
tree_str = _format_search_tools_as_tree(parsed_json, None, query)
|
|
console.print(
|
|
Panel(
|
|
Markdown(f"```\n{tree_str}\n```", code_theme=CODE_THEME),
|
|
title="[dim]Result[/dim]",
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
return
|
|
|
|
if tool_name == "execute_code" and isinstance(parsed_json, dict):
|
|
execution_time = parsed_json.get("execution_time")
|
|
time_str = f" [dim]⏱️ {execution_time:.3f}s[/dim]" if execution_time is not None else ""
|
|
|
|
has_logs = "logs" in parsed_json and parsed_json["logs"]
|
|
has_result = "result" in parsed_json and parsed_json["result"] is not None
|
|
|
|
if has_result:
|
|
# Add execution time to Result only if there are no logs (logs will get the time)
|
|
result_title = f"[dim]Result[/dim]{time_str}" if not has_logs else "[dim]Result[/dim]"
|
|
console.print(
|
|
Panel(
|
|
_render_content(parsed_json["result"]),
|
|
title=result_title,
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
|
|
if has_logs:
|
|
# Add execution time to Logs panel (last panel)
|
|
console.print(
|
|
Panel(
|
|
_render_content(parsed_json["logs"]),
|
|
title=f"[dim]Logs[/dim]{time_str}",
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
|
|
if "error" in parsed_json and parsed_json["error"] is not None:
|
|
console.print(
|
|
Panel(
|
|
_render_content(parsed_json["error"]),
|
|
title="[dim red]Error[/dim red]",
|
|
border_style="red",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
else:
|
|
console.print(
|
|
Panel(
|
|
_render_content(parsed_json),
|
|
title="[dim]Result[/dim]",
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
else:
|
|
console.print(
|
|
Panel(
|
|
_render_content(output_str),
|
|
title="[dim]Result[/dim]",
|
|
border_style="dim white",
|
|
padding=(0, 1),
|
|
)
|
|
)
|
|
|
|
# Handle chat model streaming text chunks
|
|
elif event_type == "on_chat_model_stream":
|
|
chunk_obj = event_data.get("chunk")
|
|
if chunk_obj is None:
|
|
return
|
|
|
|
# Extract text from AIMessageChunk content
|
|
text_parts = []
|
|
|
|
# Try to get content from chunk object
|
|
content = chunk_obj.content if hasattr(chunk_obj, "content") else None
|
|
# It is unbelievable, but content can be a dict or a string depending on the model.
|
|
if content is not None:
|
|
if isinstance(content, str):
|
|
text_parts.append(content)
|
|
else:
|
|
item: dict
|
|
for item in content:
|
|
# Extract text from various possible fields
|
|
if "text" in item and item.get("type") != "input_json_delta":
|
|
text_parts.append(item["text"])
|
|
|
|
text = "".join(text_parts)
|
|
if text:
|
|
# Render text as markdown with custom code block renderer
|
|
print(text, end="", flush=True)
|
|
# console.print(Markdown(text, code_theme=CODE_THEME), end="\r")
|