/ cli / commands / task_cmd / send.py
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      )