alita-sdk 0.3.379__py3-none-any.whl → 0.3.462__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.
Potentially problematic release.
This version of alita-sdk might be problematic. Click here for more details.
- alita_sdk/cli/__init__.py +10 -0
- alita_sdk/cli/__main__.py +17 -0
- alita_sdk/cli/agent_executor.py +144 -0
- alita_sdk/cli/agent_loader.py +197 -0
- alita_sdk/cli/agent_ui.py +166 -0
- alita_sdk/cli/agents.py +1069 -0
- alita_sdk/cli/callbacks.py +576 -0
- alita_sdk/cli/cli.py +159 -0
- alita_sdk/cli/config.py +153 -0
- alita_sdk/cli/formatting.py +182 -0
- alita_sdk/cli/mcp_loader.py +315 -0
- alita_sdk/cli/toolkit.py +330 -0
- alita_sdk/cli/toolkit_loader.py +55 -0
- alita_sdk/cli/tools/__init__.py +9 -0
- alita_sdk/cli/tools/filesystem.py +905 -0
- alita_sdk/configurations/bitbucket.py +95 -0
- alita_sdk/configurations/confluence.py +96 -1
- alita_sdk/configurations/gitlab.py +79 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +93 -0
- alita_sdk/configurations/zephyr_enterprise.py +93 -0
- alita_sdk/configurations/zephyr_essential.py +75 -0
- alita_sdk/runtime/clients/client.py +47 -10
- alita_sdk/runtime/clients/mcp_discovery.py +342 -0
- alita_sdk/runtime/clients/mcp_manager.py +262 -0
- alita_sdk/runtime/clients/sandbox_client.py +8 -0
- alita_sdk/runtime/langchain/assistant.py +37 -16
- alita_sdk/runtime/langchain/constants.py +6 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +4 -1
- alita_sdk/runtime/langchain/document_loaders/constants.py +28 -12
- alita_sdk/runtime/langchain/langraph_agent.py +146 -31
- alita_sdk/runtime/langchain/utils.py +39 -7
- alita_sdk/runtime/models/mcp_models.py +61 -0
- alita_sdk/runtime/toolkits/__init__.py +24 -0
- alita_sdk/runtime/toolkits/application.py +8 -1
- alita_sdk/runtime/toolkits/artifact.py +5 -6
- alita_sdk/runtime/toolkits/mcp.py +895 -0
- alita_sdk/runtime/toolkits/tools.py +137 -56
- alita_sdk/runtime/tools/__init__.py +7 -2
- alita_sdk/runtime/tools/application.py +7 -0
- alita_sdk/runtime/tools/function.py +29 -25
- alita_sdk/runtime/tools/graph.py +10 -4
- alita_sdk/runtime/tools/image_generation.py +104 -8
- alita_sdk/runtime/tools/llm.py +204 -114
- alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
- alita_sdk/runtime/tools/mcp_remote_tool.py +166 -0
- alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
- alita_sdk/runtime/tools/sandbox.py +57 -43
- alita_sdk/runtime/tools/vectorstore.py +2 -1
- alita_sdk/runtime/tools/vectorstore_base.py +19 -3
- alita_sdk/runtime/utils/mcp_oauth.py +164 -0
- alita_sdk/runtime/utils/mcp_sse_client.py +405 -0
- alita_sdk/runtime/utils/streamlit.py +34 -3
- alita_sdk/runtime/utils/toolkit_utils.py +14 -4
- alita_sdk/tools/__init__.py +46 -31
- alita_sdk/tools/ado/repos/__init__.py +1 -0
- alita_sdk/tools/ado/test_plan/__init__.py +1 -1
- alita_sdk/tools/ado/wiki/__init__.py +1 -5
- alita_sdk/tools/ado/work_item/__init__.py +1 -5
- alita_sdk/tools/ado/work_item/ado_wrapper.py +17 -8
- alita_sdk/tools/base_indexer_toolkit.py +105 -43
- alita_sdk/tools/bitbucket/__init__.py +1 -0
- alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
- alita_sdk/tools/code/sonar/__init__.py +1 -1
- alita_sdk/tools/code_indexer_toolkit.py +13 -3
- alita_sdk/tools/confluence/__init__.py +2 -2
- alita_sdk/tools/confluence/api_wrapper.py +29 -7
- alita_sdk/tools/confluence/loader.py +10 -0
- alita_sdk/tools/github/__init__.py +2 -2
- alita_sdk/tools/gitlab/__init__.py +2 -1
- alita_sdk/tools/gitlab/api_wrapper.py +11 -7
- alita_sdk/tools/gitlab_org/__init__.py +1 -2
- alita_sdk/tools/google_places/__init__.py +2 -1
- alita_sdk/tools/jira/__init__.py +1 -0
- alita_sdk/tools/jira/api_wrapper.py +1 -1
- alita_sdk/tools/memory/__init__.py +1 -1
- alita_sdk/tools/openapi/__init__.py +10 -1
- alita_sdk/tools/pandas/__init__.py +1 -1
- alita_sdk/tools/postman/__init__.py +2 -1
- alita_sdk/tools/pptx/__init__.py +2 -2
- alita_sdk/tools/qtest/__init__.py +3 -3
- alita_sdk/tools/qtest/api_wrapper.py +1708 -76
- alita_sdk/tools/rally/__init__.py +1 -2
- alita_sdk/tools/report_portal/__init__.py +1 -0
- alita_sdk/tools/salesforce/__init__.py +1 -0
- alita_sdk/tools/servicenow/__init__.py +2 -3
- alita_sdk/tools/sharepoint/__init__.py +1 -0
- alita_sdk/tools/sharepoint/api_wrapper.py +125 -34
- alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/slack/__init__.py +1 -0
- alita_sdk/tools/sql/__init__.py +2 -1
- alita_sdk/tools/testio/__init__.py +1 -0
- alita_sdk/tools/testrail/__init__.py +1 -3
- alita_sdk/tools/utils/content_parser.py +27 -16
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +18 -5
- alita_sdk/tools/xray/__init__.py +2 -1
- alita_sdk/tools/zephyr/__init__.py +2 -1
- alita_sdk/tools/zephyr_enterprise/__init__.py +1 -0
- alita_sdk/tools/zephyr_essential/__init__.py +1 -0
- alita_sdk/tools/zephyr_scale/__init__.py +1 -0
- alita_sdk/tools/zephyr_squad/__init__.py +1 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.462.dist-info}/METADATA +8 -2
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.462.dist-info}/RECORD +110 -86
- alita_sdk-0.3.462.dist-info/entry_points.txt +2 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.462.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.462.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.462.dist-info}/top_level.txt +0 -0
alita_sdk/cli/agents.py
ADDED
|
@@ -0,0 +1,1069 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent commands for Alita CLI.
|
|
3
|
+
|
|
4
|
+
Provides commands to work with agents interactively or in handoff mode,
|
|
5
|
+
supporting both platform agents and local agent definition files.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import click
|
|
10
|
+
import json
|
|
11
|
+
import logging
|
|
12
|
+
import sqlite3
|
|
13
|
+
import sys
|
|
14
|
+
from typing import Optional, Dict, Any, List
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
import yaml
|
|
17
|
+
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
from rich.panel import Panel
|
|
20
|
+
from rich.table import Table
|
|
21
|
+
from rich.markdown import Markdown
|
|
22
|
+
from rich import box
|
|
23
|
+
from rich.text import Text
|
|
24
|
+
from rich.status import Status
|
|
25
|
+
from rich.live import Live
|
|
26
|
+
|
|
27
|
+
from .cli import get_client
|
|
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
|
|
34
|
+
|
|
35
|
+
logger = logging.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
# Create a rich console for beautiful output
|
|
38
|
+
console = Console()
|
|
39
|
+
|
|
40
|
+
|
|
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.
|
|
43
|
+
|
|
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)
|
|
53
|
+
|
|
54
|
+
|
|
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.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Tuple of (agent_executor, mcp_session_manager, llm, llm_model, filesystem_tools)
|
|
62
|
+
"""
|
|
63
|
+
# Load toolkit configs
|
|
64
|
+
toolkit_configs = load_toolkit_configs(agent_def, toolkit_config)
|
|
65
|
+
|
|
66
|
+
# Load MCP tools
|
|
67
|
+
mcp_toolkit_configs = _load_mcp_tools(agent_def, config.mcp_config_path)
|
|
68
|
+
toolkit_configs.extend(mcp_toolkit_configs)
|
|
69
|
+
|
|
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
|
+
)
|
|
74
|
+
|
|
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)
|
|
83
|
+
|
|
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]")
|
|
93
|
+
|
|
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)
|
|
97
|
+
|
|
98
|
+
if not has_tools:
|
|
99
|
+
return None, None, llm, llm_model, filesystem_tools
|
|
100
|
+
|
|
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
|
+
)
|
|
128
|
+
|
|
129
|
+
return agent_executor, mcp_session_manager, llm, llm_model, filesystem_tools
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _select_agent_interactive(client, config) -> Optional[str]:
|
|
133
|
+
"""
|
|
134
|
+
Show interactive menu to select an agent from platform and local agents.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
Agent source (name/id for platform, file path for local) or None if cancelled
|
|
138
|
+
"""
|
|
139
|
+
from .config import CLIConfig
|
|
140
|
+
|
|
141
|
+
console.print("\n🤖 [bold cyan]Select an agent to chat with:[/bold cyan]\n")
|
|
142
|
+
|
|
143
|
+
agents_list = []
|
|
144
|
+
|
|
145
|
+
# Load platform agents
|
|
146
|
+
try:
|
|
147
|
+
platform_agents = client.get_list_of_apps()
|
|
148
|
+
for agent in platform_agents:
|
|
149
|
+
agents_list.append({
|
|
150
|
+
'type': 'platform',
|
|
151
|
+
'name': agent['name'],
|
|
152
|
+
'source': agent['name'],
|
|
153
|
+
'description': agent.get('description', '')[:60]
|
|
154
|
+
})
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.debug(f"Failed to load platform agents: {e}")
|
|
157
|
+
|
|
158
|
+
# Load local agents
|
|
159
|
+
agents_dir = config.agents_dir
|
|
160
|
+
search_dir = Path(agents_dir)
|
|
161
|
+
|
|
162
|
+
if search_dir.exists():
|
|
163
|
+
for pattern in ['*.agent.md', '*.agent.yaml', '*.agent.yml', '*.agent.json']:
|
|
164
|
+
for file_path in search_dir.rglob(pattern):
|
|
165
|
+
try:
|
|
166
|
+
agent_def = load_agent_definition(str(file_path))
|
|
167
|
+
agents_list.append({
|
|
168
|
+
'type': 'local',
|
|
169
|
+
'name': agent_def.get('name', file_path.stem),
|
|
170
|
+
'source': str(file_path),
|
|
171
|
+
'description': agent_def.get('description', '')[:60]
|
|
172
|
+
})
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.debug(f"Failed to load {file_path}: {e}")
|
|
175
|
+
|
|
176
|
+
if not agents_list:
|
|
177
|
+
console.print("[yellow]No agents found. Create an agent first or check your configuration.[/yellow]")
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
# Display agents with numbers using rich
|
|
181
|
+
for i, agent in enumerate(agents_list, 1):
|
|
182
|
+
agent_type = "📦 Platform" if agent['type'] == 'platform' else "📁 Local"
|
|
183
|
+
console.print(f"{i}. [[bold]{agent_type}[/bold]] [cyan]{agent['name']}[/cyan]")
|
|
184
|
+
if agent['description']:
|
|
185
|
+
console.print(f" [dim]{agent['description']}[/dim]")
|
|
186
|
+
|
|
187
|
+
console.print(f"\n[dim]0. Cancel[/dim]")
|
|
188
|
+
|
|
189
|
+
# Get user selection
|
|
190
|
+
while True:
|
|
191
|
+
try:
|
|
192
|
+
choice = input("\nSelect agent number: ").strip()
|
|
193
|
+
|
|
194
|
+
if choice == '0':
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
idx = int(choice) - 1
|
|
198
|
+
if 0 <= idx < len(agents_list):
|
|
199
|
+
selected = agents_list[idx]
|
|
200
|
+
console.print(f"\n✓ [green]Selected:[/green] [bold]{selected['name']}[/bold]")
|
|
201
|
+
return selected['source']
|
|
202
|
+
else:
|
|
203
|
+
console.print(f"[yellow]Invalid selection. Please enter a number between 0 and {len(agents_list)}[/yellow]")
|
|
204
|
+
except ValueError:
|
|
205
|
+
console.print("[yellow]Please enter a valid number[/yellow]")
|
|
206
|
+
except (KeyboardInterrupt, EOFError):
|
|
207
|
+
console.print("\n\n[dim]Cancelled.[/dim]")
|
|
208
|
+
return None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@click.group()
|
|
212
|
+
def agent():
|
|
213
|
+
"""Agent testing and interaction commands."""
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@agent.command('list')
|
|
218
|
+
@click.option('--local', is_flag=True, help='List local agent definition files')
|
|
219
|
+
@click.option('--directory', default=None, help='Directory to search for local agents (defaults to AGENTS_DIR from .env)')
|
|
220
|
+
@click.pass_context
|
|
221
|
+
def agent_list(ctx, local: bool, directory: Optional[str]):
|
|
222
|
+
"""
|
|
223
|
+
List available agents.
|
|
224
|
+
|
|
225
|
+
By default, lists agents from the platform.
|
|
226
|
+
Use --local to list agent definition files in the local directory.
|
|
227
|
+
"""
|
|
228
|
+
formatter = ctx.obj['formatter']
|
|
229
|
+
config = ctx.obj['config']
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
if local:
|
|
233
|
+
# List local agent definition files
|
|
234
|
+
if directory is None:
|
|
235
|
+
directory = config.agents_dir
|
|
236
|
+
search_dir = Path(directory)
|
|
237
|
+
|
|
238
|
+
if not search_dir.exists():
|
|
239
|
+
console.print(f"[red]Directory not found: {directory}[/red]")
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
agents = []
|
|
243
|
+
|
|
244
|
+
# Find agent definition files
|
|
245
|
+
for pattern in ['*.agent.md', '*.agent.yaml', '*.agent.yml', '*.agent.json']:
|
|
246
|
+
for file_path in search_dir.rglob(pattern):
|
|
247
|
+
try:
|
|
248
|
+
agent_def = load_agent_definition(str(file_path))
|
|
249
|
+
# Use relative path if already relative, otherwise make it relative to cwd
|
|
250
|
+
try:
|
|
251
|
+
display_path = str(file_path.relative_to(Path.cwd()))
|
|
252
|
+
except ValueError:
|
|
253
|
+
display_path = str(file_path)
|
|
254
|
+
|
|
255
|
+
agents.append({
|
|
256
|
+
'name': agent_def.get('name', file_path.stem),
|
|
257
|
+
'file': display_path,
|
|
258
|
+
'description': agent_def.get('description', '')[:80]
|
|
259
|
+
})
|
|
260
|
+
except Exception as e:
|
|
261
|
+
logger.debug(f"Failed to load {file_path}: {e}")
|
|
262
|
+
|
|
263
|
+
if not agents:
|
|
264
|
+
console.print(f"\n[yellow]No agent definition files found in {directory}[/yellow]")
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
# Display local agents in a table
|
|
268
|
+
table = Table(
|
|
269
|
+
title=f"Local Agent Definitions in {directory}",
|
|
270
|
+
show_header=True,
|
|
271
|
+
header_style="bold cyan",
|
|
272
|
+
border_style="cyan",
|
|
273
|
+
box=box.ROUNDED
|
|
274
|
+
)
|
|
275
|
+
table.add_column("Name", style="bold cyan", no_wrap=True)
|
|
276
|
+
table.add_column("File", style="dim")
|
|
277
|
+
table.add_column("Description", style="white")
|
|
278
|
+
|
|
279
|
+
for agent_info in sorted(agents, key=lambda x: x['name']):
|
|
280
|
+
table.add_row(
|
|
281
|
+
agent_info['name'],
|
|
282
|
+
agent_info['file'],
|
|
283
|
+
agent_info['description'] or "-"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
console.print("\n")
|
|
287
|
+
console.print(table)
|
|
288
|
+
console.print(f"\n[green]Total: {len(agents)} local agents[/green]")
|
|
289
|
+
|
|
290
|
+
else:
|
|
291
|
+
# List platform agents
|
|
292
|
+
client = get_client(ctx)
|
|
293
|
+
|
|
294
|
+
agents = client.get_list_of_apps()
|
|
295
|
+
|
|
296
|
+
if formatter.__class__.__name__ == 'JSONFormatter':
|
|
297
|
+
click.echo(formatter._dump({'agents': agents, 'total': len(agents)}))
|
|
298
|
+
else:
|
|
299
|
+
table = Table(
|
|
300
|
+
title="Available Platform Agents",
|
|
301
|
+
show_header=True,
|
|
302
|
+
header_style="bold cyan",
|
|
303
|
+
border_style="cyan",
|
|
304
|
+
box=box.ROUNDED
|
|
305
|
+
)
|
|
306
|
+
table.add_column("ID", style="yellow", no_wrap=True)
|
|
307
|
+
table.add_column("Name", style="bold cyan")
|
|
308
|
+
table.add_column("Description", style="white")
|
|
309
|
+
|
|
310
|
+
for agent_info in agents:
|
|
311
|
+
table.add_row(
|
|
312
|
+
str(agent_info['id']),
|
|
313
|
+
agent_info['name'],
|
|
314
|
+
agent_info.get('description', '')[:80] or "-"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
console.print("\n")
|
|
318
|
+
console.print(table)
|
|
319
|
+
console.print(f"\n[green]Total: {len(agents)} agents[/green]")
|
|
320
|
+
|
|
321
|
+
except Exception as e:
|
|
322
|
+
logger.exception("Failed to list agents")
|
|
323
|
+
error_panel = Panel(
|
|
324
|
+
str(e),
|
|
325
|
+
title="Error",
|
|
326
|
+
border_style="red",
|
|
327
|
+
box=box.ROUNDED
|
|
328
|
+
)
|
|
329
|
+
console.print(error_panel, style="red")
|
|
330
|
+
raise click.Abort()
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@agent.command('show')
|
|
334
|
+
@click.argument('agent_source')
|
|
335
|
+
@click.option('--version', help='Agent version (for platform agents)')
|
|
336
|
+
@click.pass_context
|
|
337
|
+
def agent_show(ctx, agent_source: str, version: Optional[str]):
|
|
338
|
+
"""
|
|
339
|
+
Show agent details.
|
|
340
|
+
|
|
341
|
+
AGENT_SOURCE can be:
|
|
342
|
+
- Platform agent ID or name (e.g., "123" or "my-agent")
|
|
343
|
+
- Path to local agent file (e.g., ".github/agents/sdk-dev.agent.md")
|
|
344
|
+
"""
|
|
345
|
+
formatter = ctx.obj['formatter']
|
|
346
|
+
|
|
347
|
+
try:
|
|
348
|
+
# Check if it's a file path
|
|
349
|
+
if Path(agent_source).exists():
|
|
350
|
+
# Local agent file
|
|
351
|
+
agent_def = load_agent_definition(agent_source)
|
|
352
|
+
|
|
353
|
+
if formatter.__class__.__name__ == 'JSONFormatter':
|
|
354
|
+
click.echo(formatter._dump(agent_def))
|
|
355
|
+
else:
|
|
356
|
+
# Create details panel
|
|
357
|
+
details = Text()
|
|
358
|
+
details.append("File: ", style="bold")
|
|
359
|
+
details.append(f"{agent_source}\n", style="cyan")
|
|
360
|
+
|
|
361
|
+
if agent_def.get('description'):
|
|
362
|
+
details.append("\nDescription: ", style="bold")
|
|
363
|
+
details.append(f"{agent_def['description']}\n", style="white")
|
|
364
|
+
|
|
365
|
+
if agent_def.get('model'):
|
|
366
|
+
details.append("Model: ", style="bold")
|
|
367
|
+
details.append(f"{agent_def['model']}\n", style="cyan")
|
|
368
|
+
|
|
369
|
+
if agent_def.get('tools'):
|
|
370
|
+
details.append("Tools: ", style="bold")
|
|
371
|
+
details.append(f"{', '.join(agent_def['tools'])}\n", style="cyan")
|
|
372
|
+
|
|
373
|
+
if agent_def.get('temperature') is not None:
|
|
374
|
+
details.append("Temperature: ", style="bold")
|
|
375
|
+
details.append(f"{agent_def['temperature']}\n", style="cyan")
|
|
376
|
+
|
|
377
|
+
panel = Panel(
|
|
378
|
+
details,
|
|
379
|
+
title=f"Local Agent: {agent_def.get('name', 'Unknown')}",
|
|
380
|
+
title_align="left",
|
|
381
|
+
border_style="cyan",
|
|
382
|
+
box=box.ROUNDED
|
|
383
|
+
)
|
|
384
|
+
console.print("\n")
|
|
385
|
+
console.print(panel)
|
|
386
|
+
|
|
387
|
+
if agent_def.get('system_prompt'):
|
|
388
|
+
console.print("\n[bold]System Prompt:[/bold]")
|
|
389
|
+
console.print(Panel(agent_def['system_prompt'][:500] + "...", border_style="dim", box=box.ROUNDED))
|
|
390
|
+
|
|
391
|
+
else:
|
|
392
|
+
# Platform agent
|
|
393
|
+
client = get_client(ctx)
|
|
394
|
+
|
|
395
|
+
# Try to find agent by ID or name
|
|
396
|
+
agents = client.get_list_of_apps()
|
|
397
|
+
|
|
398
|
+
agent = None
|
|
399
|
+
try:
|
|
400
|
+
agent_id = int(agent_source)
|
|
401
|
+
agent = next((a for a in agents if a['id'] == agent_id), None)
|
|
402
|
+
except ValueError:
|
|
403
|
+
agent = next((a for a in agents if a['name'] == agent_source), None)
|
|
404
|
+
|
|
405
|
+
if not agent:
|
|
406
|
+
raise click.ClickException(f"Agent '{agent_source}' not found")
|
|
407
|
+
|
|
408
|
+
# Get details
|
|
409
|
+
details = client.get_app_details(agent['id'])
|
|
410
|
+
|
|
411
|
+
if formatter.__class__.__name__ == 'JSONFormatter':
|
|
412
|
+
click.echo(formatter._dump(details))
|
|
413
|
+
else:
|
|
414
|
+
# Create platform agent details panel
|
|
415
|
+
content = Text()
|
|
416
|
+
content.append("ID: ", style="bold")
|
|
417
|
+
content.append(f"{details['id']}\n", style="yellow")
|
|
418
|
+
|
|
419
|
+
if details.get('description'):
|
|
420
|
+
content.append("\nDescription: ", style="bold")
|
|
421
|
+
content.append(f"{details['description']}\n", style="white")
|
|
422
|
+
|
|
423
|
+
panel = Panel(
|
|
424
|
+
content,
|
|
425
|
+
title=f"Agent: {details['name']}",
|
|
426
|
+
title_align="left",
|
|
427
|
+
border_style="cyan",
|
|
428
|
+
box=box.ROUNDED
|
|
429
|
+
)
|
|
430
|
+
console.print("\n")
|
|
431
|
+
console.print(panel)
|
|
432
|
+
|
|
433
|
+
# Display versions in a table
|
|
434
|
+
if details.get('versions'):
|
|
435
|
+
console.print("\n[bold]Versions:[/bold]")
|
|
436
|
+
versions_table = Table(box=box.ROUNDED, border_style="dim")
|
|
437
|
+
versions_table.add_column("Name", style="cyan")
|
|
438
|
+
versions_table.add_column("ID", style="yellow")
|
|
439
|
+
for ver in details.get('versions', []):
|
|
440
|
+
versions_table.add_row(ver['name'], str(ver['id']))
|
|
441
|
+
console.print(versions_table)
|
|
442
|
+
|
|
443
|
+
except click.ClickException:
|
|
444
|
+
raise
|
|
445
|
+
except Exception as e:
|
|
446
|
+
logger.exception("Failed to show agent details")
|
|
447
|
+
error_panel = Panel(
|
|
448
|
+
str(e),
|
|
449
|
+
title="Error",
|
|
450
|
+
border_style="red",
|
|
451
|
+
box=box.ROUNDED
|
|
452
|
+
)
|
|
453
|
+
console.print(error_panel, style="red")
|
|
454
|
+
raise click.Abort()
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@agent.command('chat')
|
|
458
|
+
@click.argument('agent_source', required=False)
|
|
459
|
+
@click.option('--version', help='Agent version (for platform agents)')
|
|
460
|
+
@click.option('--toolkit-config', multiple=True, type=click.Path(exists=True),
|
|
461
|
+
help='Toolkit configuration files (can specify multiple)')
|
|
462
|
+
@click.option('--thread-id', help='Continue existing conversation thread')
|
|
463
|
+
@click.option('--model', help='Override LLM model')
|
|
464
|
+
@click.option('--temperature', type=float, help='Override temperature')
|
|
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)')
|
|
470
|
+
@click.pass_context
|
|
471
|
+
def agent_chat(ctx, agent_source: Optional[str], version: Optional[str],
|
|
472
|
+
toolkit_config: tuple, thread_id: Optional[str],
|
|
473
|
+
model: Optional[str], temperature: Optional[float],
|
|
474
|
+
max_tokens: Optional[int], work_dir: Optional[str],
|
|
475
|
+
verbose: str):
|
|
476
|
+
"""
|
|
477
|
+
Start interactive chat with an agent.
|
|
478
|
+
|
|
479
|
+
If AGENT_SOURCE is not provided, shows an interactive menu to select from
|
|
480
|
+
available agents (both platform and local).
|
|
481
|
+
|
|
482
|
+
AGENT_SOURCE can be:
|
|
483
|
+
- Platform agent ID or name
|
|
484
|
+
- Path to local agent file
|
|
485
|
+
|
|
486
|
+
Examples:
|
|
487
|
+
|
|
488
|
+
# Interactive selection
|
|
489
|
+
alita-cli agent chat
|
|
490
|
+
|
|
491
|
+
# Chat with platform agent
|
|
492
|
+
alita-cli agent chat my-agent
|
|
493
|
+
|
|
494
|
+
# Chat with local agent
|
|
495
|
+
alita-cli agent chat .github/agents/sdk-dev.agent.md
|
|
496
|
+
|
|
497
|
+
# With toolkit configurations
|
|
498
|
+
alita-cli agent chat my-agent \\
|
|
499
|
+
--toolkit-config jira-config.json \\
|
|
500
|
+
--toolkit-config github-config.json
|
|
501
|
+
|
|
502
|
+
# With filesystem access
|
|
503
|
+
alita-cli agent chat my-agent --dir ./workspace
|
|
504
|
+
|
|
505
|
+
# Continue previous conversation
|
|
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
|
|
513
|
+
"""
|
|
514
|
+
formatter = ctx.obj['formatter']
|
|
515
|
+
config = ctx.obj['config']
|
|
516
|
+
client = get_client(ctx)
|
|
517
|
+
|
|
518
|
+
# Setup verbose level
|
|
519
|
+
show_verbose = verbose != 'quiet'
|
|
520
|
+
debug_mode = verbose == 'debug'
|
|
521
|
+
|
|
522
|
+
try:
|
|
523
|
+
# If no agent specified, show selection menu
|
|
524
|
+
if not agent_source:
|
|
525
|
+
agent_source = _select_agent_interactive(client, config)
|
|
526
|
+
if not agent_source:
|
|
527
|
+
console.print("[yellow]No agent selected. Exiting.[/yellow]")
|
|
528
|
+
return
|
|
529
|
+
|
|
530
|
+
# Load agent
|
|
531
|
+
is_local = Path(agent_source).exists()
|
|
532
|
+
|
|
533
|
+
if is_local:
|
|
534
|
+
agent_def = load_agent_definition(agent_source)
|
|
535
|
+
agent_name = agent_def.get('name', Path(agent_source).stem)
|
|
536
|
+
agent_type = "Local Agent"
|
|
537
|
+
else:
|
|
538
|
+
# Platform agent - find it
|
|
539
|
+
agents = client.get_list_of_apps()
|
|
540
|
+
agent = None
|
|
541
|
+
|
|
542
|
+
try:
|
|
543
|
+
agent_id = int(agent_source)
|
|
544
|
+
agent = next((a for a in agents if a['id'] == agent_id), None)
|
|
545
|
+
except ValueError:
|
|
546
|
+
agent = next((a for a in agents if a['name'] == agent_source), None)
|
|
547
|
+
|
|
548
|
+
if not agent:
|
|
549
|
+
raise click.ClickException(f"Agent '{agent_source}' not found")
|
|
550
|
+
|
|
551
|
+
agent_name = agent['name']
|
|
552
|
+
agent_type = "Platform Agent"
|
|
553
|
+
|
|
554
|
+
# Print nice welcome banner
|
|
555
|
+
print_welcome(agent_name, agent_type)
|
|
556
|
+
|
|
557
|
+
# Initialize conversation
|
|
558
|
+
chat_history = []
|
|
559
|
+
|
|
560
|
+
# Create memory for agent
|
|
561
|
+
from langgraph.checkpoint.sqlite import SqliteSaver
|
|
562
|
+
memory = SqliteSaver(sqlite3.connect(":memory:", check_same_thread=False))
|
|
563
|
+
|
|
564
|
+
# Create agent executor
|
|
565
|
+
if is_local:
|
|
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)
|
|
569
|
+
console.print()
|
|
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]")
|
|
572
|
+
if agent_def.get('tools'):
|
|
573
|
+
console.print(f"✓ [green]Tools:[/green] [bold]{', '.join(agent_def['tools'])}[/bold]")
|
|
574
|
+
console.print()
|
|
575
|
+
|
|
576
|
+
# Setup local agent executor (handles all config, tools, MCP, etc.)
|
|
577
|
+
try:
|
|
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
|
|
580
|
+
)
|
|
581
|
+
except Exception:
|
|
582
|
+
return
|
|
583
|
+
else:
|
|
584
|
+
# Platform agent
|
|
585
|
+
details = client.get_app_details(agent['id'])
|
|
586
|
+
|
|
587
|
+
if version:
|
|
588
|
+
version_obj = next((v for v in details['versions'] if v['name'] == version), None)
|
|
589
|
+
if not version_obj:
|
|
590
|
+
raise click.ClickException(f"Version '{version}' not found")
|
|
591
|
+
version_id = version_obj['id']
|
|
592
|
+
else:
|
|
593
|
+
# Use first version
|
|
594
|
+
version_id = details['versions'][0]['id']
|
|
595
|
+
|
|
596
|
+
# Display configuration
|
|
597
|
+
console.print()
|
|
598
|
+
console.print("✓ [green]Connected to platform agent[/green]")
|
|
599
|
+
console.print()
|
|
600
|
+
|
|
601
|
+
agent_executor = client.application(
|
|
602
|
+
application_id=agent['id'],
|
|
603
|
+
application_version_id=version_id,
|
|
604
|
+
memory=memory,
|
|
605
|
+
chat_history=chat_history
|
|
606
|
+
)
|
|
607
|
+
llm = None # Platform agents don't use direct LLM
|
|
608
|
+
|
|
609
|
+
# Interactive chat loop
|
|
610
|
+
while True:
|
|
611
|
+
try:
|
|
612
|
+
# Styled prompt
|
|
613
|
+
console.print("\n[bold bright_white]>[/bold bright_white] ", end="")
|
|
614
|
+
user_input = input().strip()
|
|
615
|
+
|
|
616
|
+
if not user_input:
|
|
617
|
+
continue
|
|
618
|
+
|
|
619
|
+
# Handle commands
|
|
620
|
+
if user_input.lower() in ['exit', 'quit']:
|
|
621
|
+
console.print("\n[bold cyan]👋 Goodbye![/bold cyan]\n")
|
|
622
|
+
break
|
|
623
|
+
|
|
624
|
+
if user_input == '/clear':
|
|
625
|
+
chat_history = []
|
|
626
|
+
console.print("[green]✓ Conversation history cleared.[/green]")
|
|
627
|
+
continue
|
|
628
|
+
|
|
629
|
+
if user_input == '/history':
|
|
630
|
+
if not chat_history:
|
|
631
|
+
console.print("[yellow]No conversation history yet.[/yellow]")
|
|
632
|
+
else:
|
|
633
|
+
console.print("\n[bold cyan]── Conversation History ──[/bold cyan]")
|
|
634
|
+
for i, msg in enumerate(chat_history, 1):
|
|
635
|
+
role = msg.get('role', 'unknown')
|
|
636
|
+
content = msg.get('content', '')
|
|
637
|
+
role_color = 'blue' if role == 'user' else 'green'
|
|
638
|
+
console.print(f"\n[bold {role_color}]{i}. {role.upper()}:[/bold {role_color}] {content[:100]}...")
|
|
639
|
+
continue
|
|
640
|
+
|
|
641
|
+
if user_input == '/save':
|
|
642
|
+
console.print("[yellow]Save to file (default: conversation.json):[/yellow] ", end="")
|
|
643
|
+
filename = input().strip()
|
|
644
|
+
filename = filename or "conversation.json"
|
|
645
|
+
with open(filename, 'w') as f:
|
|
646
|
+
json.dump({'history': chat_history}, f, indent=2)
|
|
647
|
+
console.print(f"[green]✓ Conversation saved to {filename}[/green]")
|
|
648
|
+
continue
|
|
649
|
+
|
|
650
|
+
if user_input == '/help':
|
|
651
|
+
print_help()
|
|
652
|
+
continue
|
|
653
|
+
|
|
654
|
+
# Execute agent
|
|
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', '')
|
|
658
|
+
messages = []
|
|
659
|
+
if system_prompt:
|
|
660
|
+
messages.append({"role": "system", "content": system_prompt})
|
|
661
|
+
|
|
662
|
+
# Add chat history
|
|
663
|
+
for msg in chat_history:
|
|
664
|
+
messages.append(msg)
|
|
665
|
+
|
|
666
|
+
# Add user message
|
|
667
|
+
messages.append({"role": "user", "content": user_input})
|
|
668
|
+
|
|
669
|
+
try:
|
|
670
|
+
# Try streaming if available
|
|
671
|
+
if hasattr(llm, 'stream'):
|
|
672
|
+
output_chunks = []
|
|
673
|
+
first_chunk = True
|
|
674
|
+
|
|
675
|
+
# Show spinner until first token arrives
|
|
676
|
+
status = console.status("[yellow]Thinking...[/yellow]", spinner="dots")
|
|
677
|
+
status.start()
|
|
678
|
+
|
|
679
|
+
# Stream the response token by token
|
|
680
|
+
for chunk in llm.stream(messages):
|
|
681
|
+
if hasattr(chunk, 'content'):
|
|
682
|
+
token = chunk.content
|
|
683
|
+
else:
|
|
684
|
+
token = str(chunk)
|
|
685
|
+
|
|
686
|
+
if token:
|
|
687
|
+
# Stop spinner and show agent name on first token
|
|
688
|
+
if first_chunk:
|
|
689
|
+
status.stop()
|
|
690
|
+
console.print(f"\n[bold bright_cyan]{agent_name}:[/bold bright_cyan]\n", end="")
|
|
691
|
+
first_chunk = False
|
|
692
|
+
|
|
693
|
+
console.print(token, end="", markup=False)
|
|
694
|
+
output_chunks.append(token)
|
|
695
|
+
|
|
696
|
+
# Stop status if still running (no tokens received)
|
|
697
|
+
if first_chunk:
|
|
698
|
+
status.stop()
|
|
699
|
+
console.print(f"\n[bold bright_cyan]{agent_name}:[/bold bright_cyan]\n", end="")
|
|
700
|
+
|
|
701
|
+
output = ''.join(output_chunks)
|
|
702
|
+
console.print() # New line after streaming
|
|
703
|
+
else:
|
|
704
|
+
# Fallback to non-streaming with spinner
|
|
705
|
+
with console.status("[yellow]Thinking...[/yellow]", spinner="dots"):
|
|
706
|
+
response = llm.invoke(messages)
|
|
707
|
+
if hasattr(response, 'content'):
|
|
708
|
+
output = response.content
|
|
709
|
+
else:
|
|
710
|
+
output = str(response)
|
|
711
|
+
|
|
712
|
+
# Display response after spinner stops
|
|
713
|
+
console.print(f"\n[bold bright_cyan]{agent_name}:[/bold bright_cyan]")
|
|
714
|
+
if any(marker in output for marker in ['```', '**', '##', '- ', '* ']):
|
|
715
|
+
console.print(Markdown(output))
|
|
716
|
+
else:
|
|
717
|
+
console.print(output)
|
|
718
|
+
except Exception as e:
|
|
719
|
+
console.print(f"\n[red]✗ Error: {e}[/red]\n")
|
|
720
|
+
continue
|
|
721
|
+
else:
|
|
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)
|
|
753
|
+
|
|
754
|
+
# Display response
|
|
755
|
+
console.print(f"\n[bold bright_cyan]{agent_name}:[/bold bright_cyan]")
|
|
756
|
+
if any(marker in output for marker in ['```', '**', '##', '- ', '* ']):
|
|
757
|
+
console.print(Markdown(output))
|
|
758
|
+
else:
|
|
759
|
+
console.print(output)
|
|
760
|
+
|
|
761
|
+
# Update chat history
|
|
762
|
+
chat_history.append({"role": "user", "content": user_input})
|
|
763
|
+
chat_history.append({"role": "assistant", "content": output})
|
|
764
|
+
|
|
765
|
+
except KeyboardInterrupt:
|
|
766
|
+
console.print("\n\n[yellow]Interrupted. Type 'exit' to quit or continue chatting.[/yellow]")
|
|
767
|
+
continue
|
|
768
|
+
except EOFError:
|
|
769
|
+
console.print("\n\n[bold cyan]Goodbye! 👋[/bold cyan]")
|
|
770
|
+
break
|
|
771
|
+
|
|
772
|
+
except click.ClickException:
|
|
773
|
+
raise
|
|
774
|
+
except Exception as e:
|
|
775
|
+
logger.exception("Failed to start chat")
|
|
776
|
+
error_panel = Panel(
|
|
777
|
+
str(e),
|
|
778
|
+
title="Error",
|
|
779
|
+
border_style="red",
|
|
780
|
+
box=box.ROUNDED
|
|
781
|
+
)
|
|
782
|
+
console.print(error_panel, style="red")
|
|
783
|
+
raise click.Abort()
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
@agent.command('run')
|
|
787
|
+
@click.argument('agent_source')
|
|
788
|
+
@click.argument('message')
|
|
789
|
+
@click.option('--version', help='Agent version (for platform agents)')
|
|
790
|
+
@click.option('--toolkit-config', multiple=True, type=click.Path(exists=True),
|
|
791
|
+
help='Toolkit configuration files')
|
|
792
|
+
@click.option('--model', help='Override LLM model')
|
|
793
|
+
@click.option('--temperature', type=float, help='Override temperature')
|
|
794
|
+
@click.option('--max-tokens', type=int, help='Override max tokens')
|
|
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)')
|
|
800
|
+
@click.pass_context
|
|
801
|
+
def agent_run(ctx, agent_source: str, message: str, version: Optional[str],
|
|
802
|
+
toolkit_config: tuple, model: Optional[str],
|
|
803
|
+
temperature: Optional[float], max_tokens: Optional[int],
|
|
804
|
+
save_thread: Optional[str], work_dir: Optional[str],
|
|
805
|
+
verbose: str):
|
|
806
|
+
"""
|
|
807
|
+
Run agent with a single message (handoff mode).
|
|
808
|
+
|
|
809
|
+
AGENT_SOURCE can be:
|
|
810
|
+
- Platform agent ID or name
|
|
811
|
+
- Path to local agent file
|
|
812
|
+
|
|
813
|
+
MESSAGE is the input message to send to the agent.
|
|
814
|
+
|
|
815
|
+
Examples:
|
|
816
|
+
|
|
817
|
+
# Simple query
|
|
818
|
+
alita-cli agent run my-agent "What is the status of JIRA-123?"
|
|
819
|
+
|
|
820
|
+
# With local agent
|
|
821
|
+
alita-cli agent run .github/agents/sdk-dev.agent.md \\
|
|
822
|
+
"Create a new toolkit for Stripe API"
|
|
823
|
+
|
|
824
|
+
# With toolkit configs and JSON output
|
|
825
|
+
alita-cli --output json agent run my-agent "Search for bugs" \\
|
|
826
|
+
--toolkit-config jira-config.json
|
|
827
|
+
|
|
828
|
+
# With filesystem access
|
|
829
|
+
alita-cli agent run my-agent "Analyze the code in src/" --dir ./myproject
|
|
830
|
+
|
|
831
|
+
# Save thread for continuation
|
|
832
|
+
alita-cli agent run my-agent "Start task" \\
|
|
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
|
|
840
|
+
"""
|
|
841
|
+
formatter = ctx.obj['formatter']
|
|
842
|
+
client = get_client(ctx)
|
|
843
|
+
|
|
844
|
+
# Setup verbose level
|
|
845
|
+
show_verbose = verbose != 'quiet'
|
|
846
|
+
debug_mode = verbose == 'debug'
|
|
847
|
+
|
|
848
|
+
try:
|
|
849
|
+
# Load agent
|
|
850
|
+
is_local = Path(agent_source).exists()
|
|
851
|
+
|
|
852
|
+
if is_local:
|
|
853
|
+
agent_def = load_agent_definition(agent_source)
|
|
854
|
+
agent_name = agent_def.get('name', Path(agent_source).stem)
|
|
855
|
+
|
|
856
|
+
# Create memory for agent
|
|
857
|
+
from langgraph.checkpoint.sqlite import SqliteSaver
|
|
858
|
+
memory = SqliteSaver(sqlite3.connect(":memory:", check_same_thread=False))
|
|
859
|
+
|
|
860
|
+
# Setup local agent executor (reuses same logic as agent_chat)
|
|
861
|
+
try:
|
|
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
|
|
864
|
+
)
|
|
865
|
+
except Exception as e:
|
|
866
|
+
error_panel = Panel(
|
|
867
|
+
f"Failed to setup agent: {e}",
|
|
868
|
+
title="Error",
|
|
869
|
+
border_style="red",
|
|
870
|
+
box=box.ROUNDED
|
|
871
|
+
)
|
|
872
|
+
console.print(error_panel, style="red")
|
|
873
|
+
raise click.Abort()
|
|
874
|
+
|
|
875
|
+
# Execute agent
|
|
876
|
+
if agent_executor:
|
|
877
|
+
# Setup callback for verbose output
|
|
878
|
+
from langchain_core.runnables import RunnableConfig
|
|
879
|
+
|
|
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)
|
|
924
|
+
else:
|
|
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':
|
|
934
|
+
response = llm.invoke(messages)
|
|
935
|
+
if hasattr(response, 'content'):
|
|
936
|
+
output = response.content
|
|
937
|
+
else:
|
|
938
|
+
output = str(response)
|
|
939
|
+
|
|
940
|
+
click.echo(formatter._dump({
|
|
941
|
+
'agent': agent_name,
|
|
942
|
+
'message': message,
|
|
943
|
+
'response': output
|
|
944
|
+
}))
|
|
945
|
+
else:
|
|
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)
|
|
956
|
+
|
|
957
|
+
else:
|
|
958
|
+
# Platform agent
|
|
959
|
+
agents = client.get_list_of_apps()
|
|
960
|
+
agent = None
|
|
961
|
+
|
|
962
|
+
try:
|
|
963
|
+
agent_id = int(agent_source)
|
|
964
|
+
agent = next((a for a in agents if a['id'] == agent_id), None)
|
|
965
|
+
except ValueError:
|
|
966
|
+
agent = next((a for a in agents if a['name'] == agent_source), None)
|
|
967
|
+
|
|
968
|
+
if not agent:
|
|
969
|
+
raise click.ClickException(f"Agent '{agent_source}' not found")
|
|
970
|
+
|
|
971
|
+
# Get version
|
|
972
|
+
details = client.get_app_details(agent['id'])
|
|
973
|
+
|
|
974
|
+
if version:
|
|
975
|
+
version_obj = next((v for v in details['versions'] if v['name'] == version), None)
|
|
976
|
+
if not version_obj:
|
|
977
|
+
raise click.ClickException(f"Version '{version}' not found")
|
|
978
|
+
version_id = version_obj['id']
|
|
979
|
+
else:
|
|
980
|
+
version_id = details['versions'][0]['id']
|
|
981
|
+
|
|
982
|
+
# Load toolkit configs from CLI options
|
|
983
|
+
toolkit_configs = []
|
|
984
|
+
if toolkit_config:
|
|
985
|
+
for config_path in toolkit_config:
|
|
986
|
+
toolkit_configs.append(load_toolkit_config(config_path))
|
|
987
|
+
|
|
988
|
+
# Create memory
|
|
989
|
+
from langgraph.checkpoint.sqlite import SqliteSaver
|
|
990
|
+
memory = SqliteSaver(sqlite3.connect(":memory:", check_same_thread=False))
|
|
991
|
+
|
|
992
|
+
# Create agent executor
|
|
993
|
+
agent_executor = client.application(
|
|
994
|
+
application_id=agent['id'],
|
|
995
|
+
application_version_id=version_id,
|
|
996
|
+
memory=memory
|
|
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
|
+
|
|
1007
|
+
# Execute with spinner for non-JSON output
|
|
1008
|
+
if formatter.__class__.__name__ == 'JSONFormatter':
|
|
1009
|
+
result = agent_executor.invoke({
|
|
1010
|
+
"input": [message],
|
|
1011
|
+
"chat_history": []
|
|
1012
|
+
})
|
|
1013
|
+
|
|
1014
|
+
click.echo(formatter._dump({
|
|
1015
|
+
'agent': agent['name'],
|
|
1016
|
+
'message': message,
|
|
1017
|
+
'response': result.get('output', ''),
|
|
1018
|
+
'full_result': result
|
|
1019
|
+
}))
|
|
1020
|
+
else:
|
|
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
|
+
)
|
|
1040
|
+
|
|
1041
|
+
# Display output
|
|
1042
|
+
response = result.get('output', 'No response')
|
|
1043
|
+
display_output(agent['name'], message, response)
|
|
1044
|
+
|
|
1045
|
+
# Save thread if requested
|
|
1046
|
+
if save_thread:
|
|
1047
|
+
thread_data = {
|
|
1048
|
+
'agent_id': agent['id'],
|
|
1049
|
+
'agent_name': agent['name'],
|
|
1050
|
+
'version_id': version_id,
|
|
1051
|
+
'thread_id': result.get('thread_id'),
|
|
1052
|
+
'last_message': message
|
|
1053
|
+
}
|
|
1054
|
+
with open(save_thread, 'w') as f:
|
|
1055
|
+
json.dump(thread_data, f, indent=2)
|
|
1056
|
+
logger.info(f"Thread saved to {save_thread}")
|
|
1057
|
+
|
|
1058
|
+
except click.ClickException:
|
|
1059
|
+
raise
|
|
1060
|
+
except Exception as e:
|
|
1061
|
+
logger.exception("Failed to run agent")
|
|
1062
|
+
error_panel = Panel(
|
|
1063
|
+
str(e),
|
|
1064
|
+
title="Error",
|
|
1065
|
+
border_style="red",
|
|
1066
|
+
box=box.ROUNDED
|
|
1067
|
+
)
|
|
1068
|
+
console.print(error_panel, style="red")
|
|
1069
|
+
raise click.Abort()
|