send.py
1 """ 2 CLI command for sending tasks to the webui gateway and receiving responses via SSE. 3 """ 4 import asyncio 5 import sys 6 from pathlib import Path 7 from typing import Optional, List 8 9 import click 10 import httpx 11 12 from cli.utils import error_exit 13 14 from .common import ( 15 fetch_available_agents, 16 get_agent_name_from_cards, 17 execute_task, 18 ) 19 20 21 @click.command("send") 22 @click.argument("message", required=True) 23 @click.option( 24 "--url", 25 "-u", 26 envvar="SAM_WEBUI_URL", 27 default="http://localhost:8000", 28 help="Base URL of the webui gateway (default: http://localhost:8000)", 29 ) 30 @click.option( 31 "--agent", 32 "-a", 33 envvar="SAM_AGENT", 34 default="orchestrator", 35 help="Target agent name (default: orchestrator)", 36 ) 37 @click.option( 38 "--session-id", 39 "-s", 40 default=None, 41 help="Session ID for context continuity (generates new if not provided)", 42 ) 43 @click.option( 44 "--token", 45 "-t", 46 envvar="SAM_AUTH_TOKEN", 47 default=None, 48 help="Bearer token for authentication", 49 ) 50 @click.option( 51 "--file", 52 "-f", 53 "files", 54 multiple=True, 55 type=click.Path(exists=True, dir_okay=False, resolve_path=True), 56 help="File(s) to attach (can be used multiple times)", 57 ) 58 @click.option( 59 "--timeout", 60 default=120, 61 type=int, 62 help="Timeout in seconds for SSE connection (default: 120)", 63 ) 64 @click.option( 65 "--output-dir", 66 "-o", 67 default=None, 68 type=click.Path(), 69 help="Output directory for artifacts and logs (default: /tmp/sam-task-{taskId})", 70 ) 71 @click.option( 72 "--quiet", 73 "-q", 74 is_flag=True, 75 help="Suppress streaming output, only show final result", 76 ) 77 @click.option( 78 "--no-stim", 79 is_flag=True, 80 help="Do not fetch the STIM file on completion", 81 ) 82 @click.option( 83 "--debug", 84 is_flag=True, 85 help="Enable debug output", 86 ) 87 def send_task( 88 message: str, 89 url: str, 90 agent: str, 91 session_id: Optional[str], 92 token: Optional[str], 93 files: tuple, 94 timeout: int, 95 output_dir: Optional[str], 96 quiet: bool, 97 no_stim: bool, 98 debug: bool, 99 ): 100 """ 101 Send a task to the webui gateway and stream the response. 102 103 MESSAGE is the prompt text to send to the agent. 104 105 \b 106 Examples: 107 # Basic usage 108 sam task send "What is the weather today?" 109 110 # Specify agent 111 sam task send "Analyze this data" --agent data_analyst 112 113 # With file attachment 114 sam task send "Summarize this document" --file ./document.pdf 115 116 # Continue from previous session 117 sam task send "What did we discuss?" --session-id abc-123 118 119 # Custom URL with authentication 120 sam task send "Hello" --url https://mygateway.com --token $MY_TOKEN 121 """ 122 try: 123 exit_code = asyncio.run( 124 _send_task_async( 125 message=message, 126 url=url, 127 agent=agent, 128 session_id=session_id, 129 token=token, 130 files=list(files), 131 timeout=timeout, 132 output_dir=output_dir, 133 quiet=quiet, 134 no_stim=no_stim, 135 debug=debug, 136 ) 137 ) 138 sys.exit(exit_code) 139 except KeyboardInterrupt: 140 click.echo("\n\nTask cancelled by user.") 141 sys.exit(1) 142 except Exception as e: 143 error_exit(f"Error: {e}") 144 145 146 async def _send_task_async( 147 message: str, 148 url: str, 149 agent: str, 150 session_id: Optional[str], 151 token: Optional[str], 152 files: List[str], 153 timeout: int, 154 output_dir: Optional[str], 155 quiet: bool, 156 no_stim: bool, 157 debug: bool, 158 ) -> int: 159 """Async implementation of the task send command.""" 160 161 def _debug(msg: str): 162 if debug: 163 click.echo(click.style(f"[DEBUG] {msg}", fg="yellow"), err=True) 164 165 url = url.rstrip("/") 166 _debug(f"Target URL: {url}") 167 168 # Fetch available agents and validate/resolve agent name 169 try: 170 _debug("Fetching available agents...") 171 agent_cards = await fetch_available_agents(url, token) 172 _debug(f"Found {len(agent_cards)} agents") 173 174 # Try to find the specified agent 175 resolved_agent = get_agent_name_from_cards(agent_cards, agent) 176 if resolved_agent: 177 agent = resolved_agent 178 _debug(f"Resolved agent name: {agent}") 179 else: 180 available_names = [card.get("name") for card in agent_cards if card.get("name")] 181 error_exit( 182 f"Agent '{agent}' not found. Available agents: {', '.join(available_names)}" 183 ) 184 except httpx.HTTPStatusError as e: 185 _debug(f"Could not fetch agents: {e}") 186 click.echo(click.style(f"Warning: Could not fetch agent list: {e}", fg="yellow"), err=True) 187 except httpx.ConnectError: 188 error_exit(f"Failed to connect to {url}. Is the gateway running?") 189 190 # Resolve output_dir to Path if user specified one 191 output_path = Path(output_dir) if output_dir else None 192 193 return await execute_task( 194 message=message, 195 url=url, 196 agent=agent, 197 session_id=session_id, 198 token=token, 199 files=files, 200 timeout=timeout, 201 output_dir=output_path, 202 quiet=quiet, 203 no_stim=no_stim, 204 debug=debug, 205 session_hint=" (use with --session-id to continue)", 206 )