alita-sdk 0.3.460__py3-none-any.whl → 0.3.461__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
alita_sdk/cli/agents.py CHANGED
@@ -5,10 +5,12 @@ Provides commands to work with agents interactively or in handoff mode,
5
5
  supporting both platform agents and local agent definition files.
6
6
  """
7
7
 
8
+ import asyncio
8
9
  import click
9
10
  import json
10
11
  import logging
11
12
  import sqlite3
13
+ import sys
12
14
  from typing import Optional, Dict, Any, List
13
15
  from pathlib import Path
14
16
  import yaml
@@ -23,7 +25,12 @@ from rich.status import Status
23
25
  from rich.live import Live
24
26
 
25
27
  from .cli import get_client
26
- from .config import substitute_env_vars
28
+ # Import from refactored modules
29
+ from .agent_ui import print_welcome, print_help, display_output, extract_output_from_result
30
+ from .agent_loader import load_agent_definition
31
+ from .agent_executor import create_llm_instance, create_agent_executor, create_agent_executor_with_mcp
32
+ from .toolkit_loader import load_toolkit_config, load_toolkit_configs
33
+ from .callbacks import create_cli_callback, CLICallbackHandler
27
34
 
28
35
  logger = logging.getLogger(__name__)
29
36
 
@@ -31,132 +38,95 @@ logger = logging.getLogger(__name__)
31
38
  console = Console()
32
39
 
33
40
 
34
- def _print_banner(agent_name: str, agent_type: str = "local"):
35
- """Print a nice banner for the chat session using rich."""
36
- content = Text()
37
- content.append("🤖 ALITA AGENT CHAT\n\n", style="bold cyan")
38
- content.append(f"Agent: ", style="bold")
39
- content.append(f"{agent_name}\n", style="cyan")
40
- content.append(f"Type: ", style="bold")
41
- content.append(f"{agent_type}", style="cyan")
41
+ def _load_mcp_tools(agent_def: Dict[str, Any], mcp_config_path: str) -> List[Dict[str, Any]]:
42
+ """Load MCP tools from agent definition with tool-level filtering.
42
43
 
43
- panel = Panel(
44
- content,
45
- box=box.DOUBLE,
46
- border_style="cyan",
47
- padding=(1, 2)
48
- )
49
- console.print(panel)
44
+ Args:
45
+ agent_def: Agent definition dictionary containing mcps list
46
+ mcp_config_path: Path to mcp.json configuration file (workspace-level)
47
+
48
+ Returns:
49
+ List of toolkit configurations for MCP servers
50
+ """
51
+ from .mcp_loader import load_mcp_tools
52
+ return load_mcp_tools(agent_def, mcp_config_path)
50
53
 
51
54
 
52
- def _print_help():
53
- """Print help message with commands using rich table."""
54
- table = Table(
55
- show_header=True,
56
- header_style="bold yellow",
57
- border_style="yellow",
58
- box=box.ROUNDED,
59
- title="Commands",
60
- title_style="bold yellow"
61
- )
55
+ def _setup_local_agent_executor(client, agent_def: Dict[str, Any], toolkit_config: tuple,
56
+ config, model: Optional[str], temperature: Optional[float],
57
+ max_tokens: Optional[int], memory, work_dir: Optional[str]):
58
+ """Setup local agent executor with all configurations.
62
59
 
63
- table.add_column("Command", style="cyan", no_wrap=True)
64
- table.add_column("Description", style="white")
65
-
66
- table.add_row("/clear", "Clear conversation history")
67
- table.add_row("/history", "Show conversation history")
68
- table.add_row("/save", "Save conversation to file")
69
- table.add_row("/help", "Show this help")
70
- table.add_row("exit/quit", "End conversation")
71
- table.add_row("", "")
72
- table.add_row("@", "Mention files")
73
- table.add_row("/", "Run commands")
74
-
75
- console.print(table)
76
-
77
-
78
- def load_agent_definition(file_path: str) -> Dict[str, Any]:
79
- """
80
- Load agent definition from file.
81
-
82
- Supports:
83
- - YAML files (.yaml, .yml)
84
- - JSON files (.json)
85
- - Markdown files with YAML frontmatter (.md)
86
-
87
- Args:
88
- file_path: Path to agent definition file
89
-
90
60
  Returns:
91
- Dictionary with agent configuration
61
+ Tuple of (agent_executor, mcp_session_manager, llm, llm_model, filesystem_tools)
92
62
  """
93
- path = Path(file_path)
63
+ # Load toolkit configs
64
+ toolkit_configs = load_toolkit_configs(agent_def, toolkit_config)
94
65
 
95
- if not path.exists():
96
- raise FileNotFoundError(f"Agent definition not found: {file_path}")
66
+ # Load MCP tools
67
+ mcp_toolkit_configs = _load_mcp_tools(agent_def, config.mcp_config_path)
68
+ toolkit_configs.extend(mcp_toolkit_configs)
97
69
 
98
- content = path.read_text()
70
+ # Create LLM instance
71
+ llm, llm_model, llm_temperature, llm_max_tokens = create_llm_instance(
72
+ client, model, agent_def, temperature, max_tokens
73
+ )
99
74
 
100
- # Handle markdown with YAML frontmatter
101
- if path.suffix == '.md':
102
- if content.startswith('---'):
103
- parts = content.split('---', 2)
104
- if len(parts) >= 3:
105
- frontmatter = yaml.safe_load(parts[1])
106
- system_prompt = parts[2].strip()
107
-
108
- # Apply environment variable substitution
109
- system_prompt = substitute_env_vars(system_prompt)
110
-
111
- return {
112
- 'name': frontmatter.get('name', path.stem),
113
- 'description': frontmatter.get('description', ''),
114
- 'system_prompt': system_prompt,
115
- 'model': frontmatter.get('model'),
116
- 'tools': frontmatter.get('tools', []),
117
- 'temperature': frontmatter.get('temperature'),
118
- 'max_tokens': frontmatter.get('max_tokens'),
119
- 'toolkit_configs': frontmatter.get('toolkit_configs', []),
120
- }
75
+ # Add filesystem tools if --dir is provided
76
+ filesystem_tools = None
77
+ if work_dir:
78
+ from .tools import get_filesystem_tools
79
+ preset = agent_def.get('filesystem_tools_preset')
80
+ include_tools = agent_def.get('filesystem_tools_include')
81
+ exclude_tools = agent_def.get('filesystem_tools_exclude')
82
+ filesystem_tools = get_filesystem_tools(work_dir, include_tools, exclude_tools, preset)
121
83
 
122
- # Plain markdown - use content as system prompt
123
- return {
124
- 'name': path.stem,
125
- 'system_prompt': substitute_env_vars(content),
126
- }
84
+ tool_count = len(filesystem_tools)
85
+ access_msg = f"✓ Granted filesystem access to: {work_dir} ({tool_count} tools)"
86
+ if preset:
87
+ access_msg += f" [preset: {preset}]"
88
+ if include_tools:
89
+ access_msg += f" [include: {', '.join(include_tools)}]"
90
+ if exclude_tools:
91
+ access_msg += f" [exclude: {', '.join(exclude_tools)}]"
92
+ console.print(f"[dim]{access_msg}[/dim]")
127
93
 
128
- # Handle YAML
129
- if path.suffix in ['.yaml', '.yml']:
130
- content = substitute_env_vars(content)
131
- config = yaml.safe_load(content)
132
- if 'system_prompt' in config:
133
- config['system_prompt'] = substitute_env_vars(config['system_prompt'])
134
- return config
94
+ # Check if we have tools
95
+ has_tools = bool(agent_def.get('tools') or toolkit_configs or filesystem_tools)
96
+ has_mcp = any(tc.get('toolkit_type') == 'mcp' for tc in toolkit_configs)
135
97
 
136
- # Handle JSON
137
- if path.suffix == '.json':
138
- content = substitute_env_vars(content)
139
- config = json.loads(content)
140
- if 'system_prompt' in config:
141
- config['system_prompt'] = substitute_env_vars(config['system_prompt'])
142
- return config
98
+ if not has_tools:
99
+ return None, None, llm, llm_model, filesystem_tools
143
100
 
144
- raise ValueError(f"Unsupported file format: {path.suffix}")
145
-
146
-
147
- def load_toolkit_config(file_path: str) -> Dict[str, Any]:
148
- """Load toolkit configuration from JSON file with env var substitution."""
149
- path = Path(file_path)
150
-
151
- if not path.exists():
152
- raise FileNotFoundError(f"Toolkit configuration not found: {file_path}")
153
-
154
- with open(path) as f:
155
- content = f.read()
101
+ # Create agent executor with or without MCP
102
+ mcp_session_manager = None
103
+ if has_mcp:
104
+ # Create persistent event loop for MCP tools
105
+ from alita_sdk.runtime.tools.llm import LLMNode
106
+ if not hasattr(LLMNode, '_persistent_loop') or \
107
+ LLMNode._persistent_loop is None or \
108
+ LLMNode._persistent_loop.is_closed():
109
+ LLMNode._persistent_loop = asyncio.new_event_loop()
110
+ console.print("[dim]Created persistent event loop for MCP tools[/dim]")
111
+
112
+ # Load MCP tools using persistent loop
113
+ loop = LLMNode._persistent_loop
114
+ asyncio.set_event_loop(loop)
115
+ agent_executor, mcp_session_manager = loop.run_until_complete(
116
+ create_agent_executor_with_mcp(
117
+ client, agent_def, toolkit_configs,
118
+ llm, llm_model, llm_temperature, llm_max_tokens, memory,
119
+ filesystem_tools=filesystem_tools
120
+ )
121
+ )
122
+ else:
123
+ agent_executor = create_agent_executor(
124
+ client, agent_def, toolkit_configs,
125
+ llm, llm_model, llm_temperature, llm_max_tokens, memory,
126
+ filesystem_tools=filesystem_tools
127
+ )
156
128
 
157
- # Apply environment variable substitution
158
- content = substitute_env_vars(content)
159
- return json.loads(content)
129
+ return agent_executor, mcp_session_manager, llm, llm_model, filesystem_tools
160
130
 
161
131
 
162
132
  def _select_agent_interactive(client, config) -> Optional[str]:
@@ -493,11 +463,16 @@ def agent_show(ctx, agent_source: str, version: Optional[str]):
493
463
  @click.option('--model', help='Override LLM model')
494
464
  @click.option('--temperature', type=float, help='Override temperature')
495
465
  @click.option('--max-tokens', type=int, help='Override max tokens')
466
+ @click.option('--dir', 'work_dir', type=click.Path(exists=True, file_okay=False, dir_okay=True),
467
+ help='Grant agent filesystem access to this directory')
468
+ @click.option('--verbose', '-v', type=click.Choice(['quiet', 'default', 'debug']), default='default',
469
+ help='Output verbosity level: quiet (final output only), default (tool calls + outputs), debug (all including LLM calls)')
496
470
  @click.pass_context
497
471
  def agent_chat(ctx, agent_source: Optional[str], version: Optional[str],
498
472
  toolkit_config: tuple, thread_id: Optional[str],
499
473
  model: Optional[str], temperature: Optional[float],
500
- max_tokens: Optional[int]):
474
+ max_tokens: Optional[int], work_dir: Optional[str],
475
+ verbose: str):
501
476
  """
502
477
  Start interactive chat with an agent.
503
478
 
@@ -524,13 +499,26 @@ def agent_chat(ctx, agent_source: Optional[str], version: Optional[str],
524
499
  --toolkit-config jira-config.json \\
525
500
  --toolkit-config github-config.json
526
501
 
502
+ # With filesystem access
503
+ alita-cli agent chat my-agent --dir ./workspace
504
+
527
505
  # Continue previous conversation
528
506
  alita-cli agent chat my-agent --thread-id abc123
507
+
508
+ # Quiet mode (hide tool calls and thinking)
509
+ alita-cli agent chat my-agent --verbose quiet
510
+
511
+ # Debug mode (show all including LLM calls)
512
+ alita-cli agent chat my-agent --verbose debug
529
513
  """
530
514
  formatter = ctx.obj['formatter']
531
515
  config = ctx.obj['config']
532
516
  client = get_client(ctx)
533
517
 
518
+ # Setup verbose level
519
+ show_verbose = verbose != 'quiet'
520
+ debug_mode = verbose == 'debug'
521
+
534
522
  try:
535
523
  # If no agent specified, show selection menu
536
524
  if not agent_source:
@@ -563,9 +551,8 @@ def agent_chat(ctx, agent_source: Optional[str], version: Optional[str],
563
551
  agent_name = agent['name']
564
552
  agent_type = "Platform Agent"
565
553
 
566
- # Print nice banner
567
- _print_banner(agent_name, agent_type)
568
- _print_help()
554
+ # Print nice welcome banner
555
+ print_welcome(agent_name, agent_type)
569
556
 
570
557
  # Initialize conversation
571
558
  chat_history = []
@@ -574,72 +561,25 @@ def agent_chat(ctx, agent_source: Optional[str], version: Optional[str],
574
561
  from langgraph.checkpoint.sqlite import SqliteSaver
575
562
  memory = SqliteSaver(sqlite3.connect(":memory:", check_same_thread=False))
576
563
 
577
- # Load toolkits if provided
578
- toolkit_configs = []
579
-
580
- # First load from agent definition if local
581
- if is_local and 'toolkit_configs' in agent_def:
582
- for tk_config in agent_def['toolkit_configs']:
583
- if isinstance(tk_config, dict):
584
- if 'file' in tk_config:
585
- # Load from file
586
- config = load_toolkit_config(tk_config['file'])
587
- toolkit_configs.append(config)
588
- console.print(f"[dim]Loaded toolkit config from agent definition: {tk_config['file']}[/dim]")
589
- elif 'config' in tk_config:
590
- # Inline config
591
- toolkit_configs.append(tk_config['config'])
592
- console.print(f"[dim]Loaded inline toolkit config: {tk_config['config'].get('toolkit_name', 'unknown')}[/dim]")
593
-
594
- # Then load from --toolkit-config options
595
- if toolkit_config:
596
- for config_path in toolkit_config:
597
- config = load_toolkit_config(config_path)
598
- toolkit_configs.append(config)
599
- console.print(f"[dim]Loaded toolkit config: {config_path}[/dim]")
600
-
601
- # Auto-add toolkits to tools if not already present
602
- if is_local and toolkit_configs:
603
- tools = agent_def.get('tools', [])
604
- for tk_config in toolkit_configs:
605
- toolkit_name = tk_config.get('toolkit_name')
606
- if toolkit_name and toolkit_name not in tools:
607
- tools.append(toolkit_name)
608
- console.print(f"[dim]Auto-added toolkit to tools: {toolkit_name}[/dim]")
609
- agent_def['tools'] = tools
610
-
611
564
  # Create agent executor
612
565
  if is_local:
613
- # For local agents, use direct LLM integration
614
- llm_model = model or agent_def.get('model', 'gpt-4o')
615
- llm_temperature = temperature if temperature is not None else agent_def.get('temperature', 0.7)
616
- llm_max_tokens = max_tokens or agent_def.get('max_tokens', 2000)
617
-
618
- system_prompt = agent_def.get('system_prompt', '')
619
-
620
566
  # Display configuration
567
+ llm_model_display = model or agent_def.get('model', 'gpt-4o')
568
+ llm_temperature_display = temperature if temperature is not None else agent_def.get('temperature', 0.7)
621
569
  console.print()
622
- console.print(f"✓ [green]Using model:[/green] [bold]{llm_model}[/bold]")
623
- console.print(f"✓ [green]Temperature:[/green] [bold]{llm_temperature}[/bold]")
570
+ console.print(f"✓ [green]Using model:[/green] [bold]{llm_model_display}[/bold]")
571
+ console.print(f"✓ [green]Temperature:[/green] [bold]{llm_temperature_display}[/bold]")
624
572
  if agent_def.get('tools'):
625
573
  console.print(f"✓ [green]Tools:[/green] [bold]{', '.join(agent_def['tools'])}[/bold]")
626
574
  console.print()
627
575
 
628
- # Create LLM instance using AlitaClient
576
+ # Setup local agent executor (handles all config, tools, MCP, etc.)
629
577
  try:
630
- llm = client.get_llm(
631
- model_name=llm_model,
632
- model_config={
633
- 'temperature': llm_temperature,
634
- 'max_tokens': llm_max_tokens
635
- }
578
+ agent_executor, mcp_session_manager, llm, llm_model, filesystem_tools = _setup_local_agent_executor(
579
+ client, agent_def, toolkit_config, config, model, temperature, max_tokens, memory, work_dir
636
580
  )
637
- except Exception as e:
638
- console.print(f"\n✗ [red]Failed to create LLM instance:[/red] {e}")
639
- console.print("[yellow]Hint: Make sure OPENAI_API_KEY or other LLM credentials are set[/yellow]")
581
+ except Exception:
640
582
  return
641
-
642
- agent_executor = None # Local agents use direct LLM calls
643
583
  else:
644
584
  # Platform agent
645
585
  details = client.get_app_details(agent['id'])
@@ -708,12 +648,13 @@ def agent_chat(ctx, agent_source: Optional[str], version: Optional[str],
708
648
  continue
709
649
 
710
650
  if user_input == '/help':
711
- _print_help()
651
+ print_help()
712
652
  continue
713
653
 
714
654
  # Execute agent
715
- if is_local:
716
- # Local agent: use direct LLM call with streaming
655
+ if is_local and agent_executor is None:
656
+ # Local agent without tools: use direct LLM call with streaming
657
+ system_prompt = agent_def.get('system_prompt', '')
717
658
  messages = []
718
659
  if system_prompt:
719
660
  messages.append({"role": "system", "content": system_prompt})
@@ -778,13 +719,37 @@ def agent_chat(ctx, agent_source: Optional[str], version: Optional[str],
778
719
  console.print(f"\n[red]✗ Error: {e}[/red]\n")
779
720
  continue
780
721
  else:
781
- # Platform agent: use agent executor
782
- with console.status("[yellow]Thinking...[/yellow]", spinner="dots"):
783
- result = agent_executor.invoke({
784
- "input": [user_input],
785
- "chat_history": chat_history
786
- })
787
- output = result.get('output', '')
722
+ # Agent with tools or platform agent: use agent executor
723
+ # Setup callback for verbose output
724
+ from langchain_core.runnables import RunnableConfig
725
+
726
+ invoke_config = None
727
+ if show_verbose:
728
+ cli_callback = create_cli_callback(verbose=True, debug=debug_mode)
729
+ invoke_config = RunnableConfig(callbacks=[cli_callback])
730
+
731
+ # Show status only when not verbose (verbose shows its own progress)
732
+ if not show_verbose:
733
+ with console.status("[yellow]Thinking...[/yellow]", spinner="dots"):
734
+ result = agent_executor.invoke(
735
+ {
736
+ "input": [user_input] if not is_local else user_input,
737
+ "chat_history": chat_history
738
+ },
739
+ config=invoke_config
740
+ )
741
+ else:
742
+ console.print() # Add spacing before tool calls
743
+ result = agent_executor.invoke(
744
+ {
745
+ "input": [user_input] if not is_local else user_input,
746
+ "chat_history": chat_history
747
+ },
748
+ config=invoke_config
749
+ )
750
+
751
+ # Extract output from result
752
+ output = extract_output_from_result(result)
788
753
 
789
754
  # Display response
790
755
  console.print(f"\n[bold bright_cyan]{agent_name}:[/bold bright_cyan]")
@@ -828,11 +793,16 @@ def agent_chat(ctx, agent_source: Optional[str], version: Optional[str],
828
793
  @click.option('--temperature', type=float, help='Override temperature')
829
794
  @click.option('--max-tokens', type=int, help='Override max tokens')
830
795
  @click.option('--save-thread', help='Save thread ID to file for continuation')
796
+ @click.option('--dir', 'work_dir', type=click.Path(exists=True, file_okay=False, dir_okay=True),
797
+ help='Grant agent filesystem access to this directory')
798
+ @click.option('--verbose', '-v', type=click.Choice(['quiet', 'default', 'debug']), default='default',
799
+ help='Output verbosity level: quiet (final output only), default (tool calls + outputs), debug (all including LLM calls)')
831
800
  @click.pass_context
832
801
  def agent_run(ctx, agent_source: str, message: str, version: Optional[str],
833
802
  toolkit_config: tuple, model: Optional[str],
834
803
  temperature: Optional[float], max_tokens: Optional[int],
835
- save_thread: Optional[str]):
804
+ save_thread: Optional[str], work_dir: Optional[str],
805
+ verbose: str):
836
806
  """
837
807
  Run agent with a single message (handoff mode).
838
808
 
@@ -855,58 +825,46 @@ def agent_run(ctx, agent_source: str, message: str, version: Optional[str],
855
825
  alita-cli --output json agent run my-agent "Search for bugs" \\
856
826
  --toolkit-config jira-config.json
857
827
 
828
+ # With filesystem access
829
+ alita-cli agent run my-agent "Analyze the code in src/" --dir ./myproject
830
+
858
831
  # Save thread for continuation
859
832
  alita-cli agent run my-agent "Start task" \\
860
833
  --save-thread thread.txt
834
+
835
+ # Quiet mode (hide tool calls and thinking)
836
+ alita-cli agent run my-agent "Query" --verbose quiet
837
+
838
+ # Debug mode (show all including LLM calls)
839
+ alita-cli agent run my-agent "Query" --verbose debug
861
840
  """
862
841
  formatter = ctx.obj['formatter']
863
842
  client = get_client(ctx)
864
843
 
844
+ # Setup verbose level
845
+ show_verbose = verbose != 'quiet'
846
+ debug_mode = verbose == 'debug'
847
+
865
848
  try:
866
849
  # Load agent
867
850
  is_local = Path(agent_source).exists()
868
851
 
869
- # Load toolkits
870
- toolkit_configs = []
871
-
872
852
  if is_local:
873
853
  agent_def = load_agent_definition(agent_source)
874
854
  agent_name = agent_def.get('name', Path(agent_source).stem)
875
855
 
876
- # Load toolkit configs from agent definition
877
- if 'toolkit_configs' in agent_def:
878
- for tk_config in agent_def['toolkit_configs']:
879
- if isinstance(tk_config, dict):
880
- if 'file' in tk_config:
881
- config = load_toolkit_config(tk_config['file'])
882
- toolkit_configs.append(config)
883
- elif 'config' in tk_config:
884
- toolkit_configs.append(tk_config['config'])
885
-
886
- # Load additional toolkit configs from --toolkit-config options
887
- if toolkit_config:
888
- for config_path in toolkit_config:
889
- config = load_toolkit_config(config_path)
890
- toolkit_configs.append(config)
891
-
892
- # Get LLM configuration
893
- llm_model = model or agent_def.get('model', 'gpt-4o')
894
- llm_temperature = temperature if temperature is not None else agent_def.get('temperature', 0.7)
895
- llm_max_tokens = max_tokens or agent_def.get('max_tokens', 2000)
896
- system_prompt = agent_def.get('system_prompt', '')
856
+ # Create memory for agent
857
+ from langgraph.checkpoint.sqlite import SqliteSaver
858
+ memory = SqliteSaver(sqlite3.connect(":memory:", check_same_thread=False))
897
859
 
898
- # Create LLM instance
860
+ # Setup local agent executor (reuses same logic as agent_chat)
899
861
  try:
900
- llm = client.get_llm(
901
- model_name=llm_model,
902
- model_config={
903
- 'temperature': llm_temperature,
904
- 'max_tokens': llm_max_tokens
905
- }
862
+ agent_executor, mcp_session_manager, llm, llm_model, filesystem_tools = _setup_local_agent_executor(
863
+ client, agent_def, toolkit_config, ctx.obj['config'], model, temperature, max_tokens, memory, work_dir
906
864
  )
907
865
  except Exception as e:
908
866
  error_panel = Panel(
909
- f"Failed to create LLM instance: {e}",
867
+ f"Failed to setup agent: {e}",
910
868
  title="Error",
911
869
  border_style="red",
912
870
  box=box.ROUNDED
@@ -914,44 +872,87 @@ def agent_run(ctx, agent_source: str, message: str, version: Optional[str],
914
872
  console.print(error_panel, style="red")
915
873
  raise click.Abort()
916
874
 
917
- # Prepare messages
918
- messages = []
919
- if system_prompt:
920
- messages.append({"role": "system", "content": system_prompt})
921
- messages.append({"role": "user", "content": message})
922
-
923
- # Execute with spinner for non-JSON output
924
- if formatter.__class__.__name__ == 'JSONFormatter':
925
- response = llm.invoke(messages)
926
- if hasattr(response, 'content'):
927
- output = response.content
928
- else:
929
- output = str(response)
875
+ # Execute agent
876
+ if agent_executor:
877
+ # Setup callback for verbose output
878
+ from langchain_core.runnables import RunnableConfig
930
879
 
931
- click.echo(formatter._dump({
932
- 'agent': agent_name,
933
- 'message': message,
934
- 'response': output
935
- }))
880
+ invoke_config = None
881
+ if show_verbose:
882
+ cli_callback = create_cli_callback(verbose=True, debug=debug_mode)
883
+ invoke_config = RunnableConfig(callbacks=[cli_callback])
884
+
885
+ # Execute with spinner for non-JSON output
886
+ if formatter.__class__.__name__ == 'JSONFormatter':
887
+ # JSON output: always quiet, no callbacks
888
+ with console.status("[yellow]Processing...[/yellow]", spinner="dots"):
889
+ result = agent_executor.invoke({
890
+ "input": message,
891
+ "chat_history": []
892
+ })
893
+
894
+ click.echo(formatter._dump({
895
+ 'agent': agent_name,
896
+ 'message': message,
897
+ 'response': extract_output_from_result(result),
898
+ 'full_result': result
899
+ }))
900
+ else:
901
+ # Show status only when not verbose (verbose shows its own progress)
902
+ if not show_verbose:
903
+ with console.status("[yellow]Processing...[/yellow]", spinner="dots"):
904
+ result = agent_executor.invoke(
905
+ {
906
+ "input": message,
907
+ "chat_history": []
908
+ },
909
+ config=invoke_config
910
+ )
911
+ else:
912
+ console.print() # Add spacing before tool calls
913
+ result = agent_executor.invoke(
914
+ {
915
+ "input": message,
916
+ "chat_history": []
917
+ },
918
+ config=invoke_config
919
+ )
920
+
921
+ # Extract and display output
922
+ output = extract_output_from_result(result)
923
+ display_output(agent_name, message, output)
936
924
  else:
937
- # Show spinner while executing
938
- with console.status("[yellow]Processing...[/yellow]", spinner="dots"):
925
+ # Simple LLM mode without tools
926
+ system_prompt = agent_def.get('system_prompt', '')
927
+ messages = []
928
+ if system_prompt:
929
+ messages.append({"role": "system", "content": system_prompt})
930
+ messages.append({"role": "user", "content": message})
931
+
932
+ # Execute with spinner for non-JSON output
933
+ if formatter.__class__.__name__ == 'JSONFormatter':
939
934
  response = llm.invoke(messages)
940
935
  if hasattr(response, 'content'):
941
936
  output = response.content
942
937
  else:
943
938
  output = str(response)
944
-
945
- # Format and display output
946
- console.print(f"\n[bold cyan]🤖 Agent: {agent_name}[/bold cyan]\n")
947
- console.print(f"[bold]Message:[/bold] {message}\n")
948
- console.print("[bold]Response:[/bold]")
949
- # Render markdown if the response looks like it contains markdown
950
- if any(marker in output for marker in ['```', '**', '##', '- ', '* ']):
951
- console.print(Markdown(output))
939
+
940
+ click.echo(formatter._dump({
941
+ 'agent': agent_name,
942
+ 'message': message,
943
+ 'response': output
944
+ }))
952
945
  else:
953
- console.print(output)
954
- console.print()
946
+ # Show spinner while executing
947
+ with console.status("[yellow]Processing...[/yellow]", spinner="dots"):
948
+ response = llm.invoke(messages)
949
+ if hasattr(response, 'content'):
950
+ output = response.content
951
+ else:
952
+ output = str(response)
953
+
954
+ # Display output
955
+ display_output(agent_name, message, output)
955
956
 
956
957
  else:
957
958
  # Platform agent
@@ -978,11 +979,11 @@ def agent_run(ctx, agent_source: str, message: str, version: Optional[str],
978
979
  else:
979
980
  version_id = details['versions'][0]['id']
980
981
 
981
- # Load additional toolkit configs from --toolkit-config options
982
+ # Load toolkit configs from CLI options
983
+ toolkit_configs = []
982
984
  if toolkit_config:
983
985
  for config_path in toolkit_config:
984
- config = load_toolkit_config(config_path)
985
- toolkit_configs.append(config)
986
+ toolkit_configs.append(load_toolkit_config(config_path))
986
987
 
987
988
  # Create memory
988
989
  from langgraph.checkpoint.sqlite import SqliteSaver
@@ -995,6 +996,14 @@ def agent_run(ctx, agent_source: str, message: str, version: Optional[str],
995
996
  memory=memory
996
997
  )
997
998
 
999
+ # Setup callback for verbose output
1000
+ from langchain_core.runnables import RunnableConfig
1001
+
1002
+ invoke_config = None
1003
+ if show_verbose:
1004
+ cli_callback = create_cli_callback(verbose=True, debug=debug_mode)
1005
+ invoke_config = RunnableConfig(callbacks=[cli_callback])
1006
+
998
1007
  # Execute with spinner for non-JSON output
999
1008
  if formatter.__class__.__name__ == 'JSONFormatter':
1000
1009
  result = agent_executor.invoke({
@@ -1009,24 +1018,29 @@ def agent_run(ctx, agent_source: str, message: str, version: Optional[str],
1009
1018
  'full_result': result
1010
1019
  }))
1011
1020
  else:
1012
- # Show spinner while executing
1013
- with console.status("[yellow]Processing...[/yellow]", spinner="dots"):
1014
- result = agent_executor.invoke({
1015
- "input": [message],
1016
- "chat_history": []
1017
- })
1021
+ # Show status only when not verbose
1022
+ if not show_verbose:
1023
+ with console.status("[yellow]Processing...[/yellow]", spinner="dots"):
1024
+ result = agent_executor.invoke(
1025
+ {
1026
+ "input": [message],
1027
+ "chat_history": []
1028
+ },
1029
+ config=invoke_config
1030
+ )
1031
+ else:
1032
+ console.print() # Add spacing before tool calls
1033
+ result = agent_executor.invoke(
1034
+ {
1035
+ "input": [message],
1036
+ "chat_history": []
1037
+ },
1038
+ config=invoke_config
1039
+ )
1018
1040
 
1019
- # Format and display output
1020
- console.print(f"\n[bold cyan]🤖 Agent: {agent['name']}[/bold cyan]\n")
1021
- console.print(f"[bold]Message:[/bold] {message}\n")
1022
- console.print("[bold]Response:[/bold]")
1041
+ # Display output
1023
1042
  response = result.get('output', 'No response')
1024
- # Render markdown if the response looks like it contains markdown
1025
- if any(marker in response for marker in ['```', '**', '##', '- ', '* ']):
1026
- console.print(Markdown(response))
1027
- else:
1028
- console.print(response)
1029
- console.print()
1043
+ display_output(agent['name'], message, response)
1030
1044
 
1031
1045
  # Save thread if requested
1032
1046
  if save_thread: