Add tool composition UI with auto-dependency management
- Add ToolStepDialog for configuring tool steps (tool selection, input mapping, args) - Add "Add Tool" button in Tool Builder alongside Add Prompt/Add Code - Add ToolNode in flow graph visualization (purple puzzle piece icon) - Auto-populate dependencies when adding ToolStep in GUI - Add --auto-install flag for automatic runtime dependency installation - Add check_dependencies() and auto_install_dependencies() functions - Update runner to pass auto_install parameter through execution chain Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
+50
-4
@@ -7,7 +7,7 @@ from typing import Optional
|
||||
|
||||
from .tool import Tool, PromptStep, CodeStep, ToolStep
|
||||
from .providers import call_provider, mock_provider
|
||||
from .resolver import resolve_tool, ToolNotFoundError, ToolSpec
|
||||
from .resolver import resolve_tool, ToolNotFoundError, ToolSpec, install_from_registry
|
||||
from .manifest import load_manifest
|
||||
from .profiles import load_profile
|
||||
|
||||
@@ -15,6 +15,36 @@ from .profiles import load_profile
|
||||
MAX_TOOL_DEPTH = 10
|
||||
|
||||
|
||||
def auto_install_dependencies(tool: Tool, verbose: bool = False) -> list[str]:
|
||||
"""
|
||||
Automatically install missing dependencies for a tool.
|
||||
|
||||
Args:
|
||||
tool: Tool to check and install dependencies for
|
||||
verbose: Show installation progress
|
||||
|
||||
Returns:
|
||||
List of successfully installed tool references
|
||||
"""
|
||||
missing = check_dependencies(tool)
|
||||
if not missing:
|
||||
return []
|
||||
|
||||
installed = []
|
||||
for dep in missing:
|
||||
try:
|
||||
if verbose:
|
||||
print(f"[auto-install] Installing {dep}...", file=sys.stderr)
|
||||
install_from_registry(dep)
|
||||
installed.append(dep)
|
||||
if verbose:
|
||||
print(f"[auto-install] Installed {dep}", file=sys.stderr)
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to auto-install {dep}: {e}", file=sys.stderr)
|
||||
|
||||
return installed
|
||||
|
||||
|
||||
def check_dependencies(tool: Tool, checked: set = None) -> list[str]:
|
||||
"""
|
||||
Check if all dependencies for a tool are available.
|
||||
@@ -293,6 +323,7 @@ def run_tool(
|
||||
dry_run: bool = False,
|
||||
show_prompt: bool = False,
|
||||
verbose: bool = False,
|
||||
auto_install: bool = False,
|
||||
_depth: int = 0,
|
||||
_call_stack: Optional[list] = None
|
||||
) -> tuple[str, int]:
|
||||
@@ -307,6 +338,7 @@ def run_tool(
|
||||
dry_run: Just show what would happen
|
||||
show_prompt: Show prompts in addition to output
|
||||
verbose: Show debug info
|
||||
auto_install: Automatically install missing dependencies
|
||||
_depth: Internal recursion depth tracker
|
||||
_call_stack: Internal call stack for error reporting
|
||||
|
||||
@@ -322,8 +354,19 @@ def run_tool(
|
||||
if _depth == 0 and (tool.dependencies or any(isinstance(s, ToolStep) for s in tool.steps)):
|
||||
missing = check_dependencies(tool)
|
||||
if missing:
|
||||
print(f"Warning: Missing dependencies: {', '.join(missing)}", file=sys.stderr)
|
||||
print(f"Install with: cmdforge install {' '.join(missing)}", file=sys.stderr)
|
||||
if auto_install:
|
||||
# Try to auto-install missing dependencies
|
||||
installed = auto_install_dependencies(tool, verbose=verbose)
|
||||
# Re-check after installation
|
||||
still_missing = check_dependencies(tool)
|
||||
if still_missing:
|
||||
print(f"Warning: Could not install all dependencies. Missing: {', '.join(still_missing)}", file=sys.stderr)
|
||||
elif installed:
|
||||
print(f"Auto-installed dependencies: {', '.join(installed)}", file=sys.stderr)
|
||||
else:
|
||||
print(f"Warning: Missing dependencies: {', '.join(missing)}", file=sys.stderr)
|
||||
print(f"Install with: cmdforge install {' '.join(missing)}", file=sys.stderr)
|
||||
print(f"Or use --auto-install to install automatically", file=sys.stderr)
|
||||
# Continue anyway - the actual step execution will fail with a better error
|
||||
|
||||
# Initialize variables with input and arguments
|
||||
@@ -448,6 +491,8 @@ def create_argument_parser(tool: Tool) -> argparse.ArgumentParser:
|
||||
help="Override provider (e.g., --provider mock)")
|
||||
parser.add_argument("-v", "--verbose", action="store_true",
|
||||
help="Show debug information")
|
||||
parser.add_argument("--auto-install", action="store_true",
|
||||
help="Automatically install missing tool dependencies")
|
||||
|
||||
# Tool-specific flags from arguments
|
||||
for arg in tool.arguments:
|
||||
@@ -527,7 +572,8 @@ def main():
|
||||
provider_override=effective_provider,
|
||||
dry_run=args.dry_run,
|
||||
show_prompt=args.show_prompt,
|
||||
verbose=args.verbose
|
||||
verbose=args.verbose,
|
||||
auto_install=args.auto_install
|
||||
)
|
||||
|
||||
# Write output
|
||||
|
||||
Reference in New Issue
Block a user