ambivo-agents 1.3.3__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.
- ambivo_agents/__init__.py +89 -0
- ambivo_agents/agents/__init__.py +19 -0
- ambivo_agents/agents/assistant.py +79 -0
- ambivo_agents/agents/code_executor.py +133 -0
- ambivo_agents/agents/knowledge_base.py +595 -0
- ambivo_agents/agents/media_editor.py +777 -0
- ambivo_agents/agents/simple_web_search.py +404 -0
- ambivo_agents/agents/web_scraper.py +682 -0
- ambivo_agents/agents/web_search.py +660 -0
- ambivo_agents/agents/youtube_download.py +553 -0
- ambivo_agents/cli.py +1871 -0
- ambivo_agents/config/__init__.py +4 -0
- ambivo_agents/config/loader.py +301 -0
- ambivo_agents/core/__init__.py +33 -0
- ambivo_agents/core/base.py +880 -0
- ambivo_agents/core/llm.py +333 -0
- ambivo_agents/core/memory.py +640 -0
- ambivo_agents/executors/__init__.py +8 -0
- ambivo_agents/executors/docker_executor.py +108 -0
- ambivo_agents/executors/media_executor.py +237 -0
- ambivo_agents/executors/youtube_executor.py +404 -0
- ambivo_agents/services/__init__.py +6 -0
- ambivo_agents/services/agent_service.py +590 -0
- ambivo_agents/services/factory.py +366 -0
- ambivo_agents-1.3.3.dist-info/METADATA +773 -0
- ambivo_agents-1.3.3.dist-info/RECORD +30 -0
- ambivo_agents-1.3.3.dist-info/WHEEL +5 -0
- ambivo_agents-1.3.3.dist-info/entry_points.txt +3 -0
- ambivo_agents-1.3.3.dist-info/licenses/LICENSE +21 -0
- ambivo_agents-1.3.3.dist-info/top_level.txt +1 -0
ambivo_agents/cli.py
ADDED
@@ -0,0 +1,1871 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Ambivo Agents CLI Interface - Version 1.0 with Agent Caching and Memory Persistence
|
4
|
+
|
5
|
+
Author: Hemant Gosain 'Sunny'
|
6
|
+
Company: Ambivo
|
7
|
+
Email: sgosain@ambivo.com
|
8
|
+
License: MIT
|
9
|
+
"""
|
10
|
+
|
11
|
+
import asyncio
|
12
|
+
import click
|
13
|
+
import json
|
14
|
+
import sys
|
15
|
+
import time
|
16
|
+
import yaml
|
17
|
+
import os
|
18
|
+
import uuid
|
19
|
+
from pathlib import Path
|
20
|
+
from datetime import datetime
|
21
|
+
from typing import Optional, Dict, Any, Union, Tuple
|
22
|
+
|
23
|
+
# Import agents directly using clean imports
|
24
|
+
from ambivo_agents import (
|
25
|
+
AssistantAgent,
|
26
|
+
KnowledgeBaseAgent,
|
27
|
+
YouTubeDownloadAgent,
|
28
|
+
MediaEditorAgent,
|
29
|
+
WebSearchAgent,
|
30
|
+
WebScraperAgent,
|
31
|
+
CodeExecutorAgent
|
32
|
+
)
|
33
|
+
|
34
|
+
# Import AgentSession with fallback
|
35
|
+
try:
|
36
|
+
from ambivo_agents import AgentSession
|
37
|
+
|
38
|
+
AGENT_SESSION_AVAILABLE = True
|
39
|
+
except ImportError:
|
40
|
+
try:
|
41
|
+
from ambivo_agents.core.base import AgentSession
|
42
|
+
|
43
|
+
AGENT_SESSION_AVAILABLE = True
|
44
|
+
except ImportError:
|
45
|
+
AGENT_SESSION_AVAILABLE = False
|
46
|
+
AgentSession = None
|
47
|
+
|
48
|
+
# Fallback to service for complex routing if needed
|
49
|
+
try:
|
50
|
+
from ambivo_agents.services import create_agent_service
|
51
|
+
|
52
|
+
SERVICE_AVAILABLE = True
|
53
|
+
except ImportError:
|
54
|
+
SERVICE_AVAILABLE = False
|
55
|
+
|
56
|
+
|
57
|
+
class ConfigManager:
|
58
|
+
"""Manages YAML configuration for Ambivo Agents"""
|
59
|
+
|
60
|
+
def __init__(self, config_path: Optional[str] = None):
|
61
|
+
self.config_path = config_path
|
62
|
+
self.config = self._load_default_config()
|
63
|
+
self._load_config()
|
64
|
+
|
65
|
+
def _load_default_config(self) -> Dict[str, Any]:
|
66
|
+
"""Load default configuration"""
|
67
|
+
return {
|
68
|
+
'cli': {
|
69
|
+
'version': '1.0.0',
|
70
|
+
'default_mode': 'shell',
|
71
|
+
'auto_session': True,
|
72
|
+
'session_prefix': 'ambivo',
|
73
|
+
'verbose': False,
|
74
|
+
'theme': 'default'
|
75
|
+
},
|
76
|
+
'agents': {
|
77
|
+
'youtube': {
|
78
|
+
'default_audio_only': True,
|
79
|
+
'output_directory': './downloads',
|
80
|
+
'max_concurrent_downloads': 3
|
81
|
+
},
|
82
|
+
'media': {
|
83
|
+
'temp_directory': './temp',
|
84
|
+
'supported_formats': ['mp4', 'avi', 'mov', 'mp3', 'wav'],
|
85
|
+
'ffmpeg_path': 'ffmpeg'
|
86
|
+
},
|
87
|
+
'knowledge_base': {
|
88
|
+
'default_embedding_model': 'sentence-transformers/all-MiniLM-L6-v2',
|
89
|
+
'chunk_size': 1000,
|
90
|
+
'chunk_overlap': 200
|
91
|
+
},
|
92
|
+
'web_search': {
|
93
|
+
'default_max_results': 5,
|
94
|
+
'timeout': 30,
|
95
|
+
'providers': ['brave', 'duckduckgo']
|
96
|
+
},
|
97
|
+
'web_scraper': {
|
98
|
+
'user_agent': 'Ambivo-Agent/1.0',
|
99
|
+
'timeout': 30,
|
100
|
+
'max_retries': 3
|
101
|
+
},
|
102
|
+
'code_executor': {
|
103
|
+
'docker_enabled': False,
|
104
|
+
'allowed_languages': ['python', 'javascript', 'bash'],
|
105
|
+
'timeout': 300
|
106
|
+
}
|
107
|
+
},
|
108
|
+
'session': {
|
109
|
+
'auto_cleanup': True,
|
110
|
+
'session_timeout': 3600,
|
111
|
+
'max_sessions': 10
|
112
|
+
},
|
113
|
+
'logging': {
|
114
|
+
'level': 'INFO',
|
115
|
+
'file': './logs/ambivo-agents.log',
|
116
|
+
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
def _load_config(self):
|
121
|
+
"""Load configuration from file, prioritizing agent_config.yaml"""
|
122
|
+
if not self.config_path:
|
123
|
+
# Try agent_config.yaml first, then other locations
|
124
|
+
possible_paths = [
|
125
|
+
'./agent_config.yaml',
|
126
|
+
'./agent_config.yml',
|
127
|
+
'~/.ambivo/agent_config.yaml',
|
128
|
+
'~/.ambivo/agent_config.yml',
|
129
|
+
'./ambivo-agents.yaml',
|
130
|
+
'./ambivo-agents.yml',
|
131
|
+
'~/.ambivo/config.yaml',
|
132
|
+
'~/.ambivo/config.yml',
|
133
|
+
'/etc/ambivo/config.yaml'
|
134
|
+
]
|
135
|
+
|
136
|
+
for path in possible_paths:
|
137
|
+
expanded_path = Path(path).expanduser()
|
138
|
+
if expanded_path.exists():
|
139
|
+
self.config_path = str(expanded_path)
|
140
|
+
break
|
141
|
+
|
142
|
+
if self.config_path and Path(self.config_path).exists():
|
143
|
+
try:
|
144
|
+
with open(self.config_path, 'r') as f:
|
145
|
+
file_config = yaml.safe_load(f)
|
146
|
+
if file_config:
|
147
|
+
self._merge_config(self.config, file_config)
|
148
|
+
click.echo(f"š Loaded configuration from: {self.config_path}")
|
149
|
+
return
|
150
|
+
except Exception as e:
|
151
|
+
click.echo(f"ā ļø Warning: Failed to load config from {self.config_path}: {e}")
|
152
|
+
|
153
|
+
# If no config found, prompt to create agent_config.yaml
|
154
|
+
if not self.config_path:
|
155
|
+
self._prompt_for_config_creation()
|
156
|
+
|
157
|
+
def _prompt_for_config_creation(self):
|
158
|
+
"""Prompt user to create agent_config.yaml"""
|
159
|
+
click.echo("š No configuration file found.")
|
160
|
+
click.echo("š” Would you like to create agent_config.yaml with default settings?")
|
161
|
+
|
162
|
+
if click.confirm("Create agent_config.yaml?"):
|
163
|
+
config_path = "./agent_config.yaml"
|
164
|
+
if self.save_sample_config(config_path):
|
165
|
+
click.echo(f"ā
Created configuration file: {config_path}")
|
166
|
+
click.echo("š” Edit this file to customize your settings")
|
167
|
+
self.config_path = config_path
|
168
|
+
else:
|
169
|
+
click.echo("ā Failed to create configuration file")
|
170
|
+
click.echo("š” Continuing with default settings")
|
171
|
+
else:
|
172
|
+
click.echo("š” Continuing with default settings")
|
173
|
+
|
174
|
+
def _merge_config(self, base: Dict, override: Dict):
|
175
|
+
"""Recursively merge configuration dictionaries"""
|
176
|
+
for key, value in override.items():
|
177
|
+
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
|
178
|
+
self._merge_config(base[key], value)
|
179
|
+
else:
|
180
|
+
base[key] = value
|
181
|
+
|
182
|
+
def get(self, path: str, default=None):
|
183
|
+
"""Get configuration value using dot notation (e.g., 'agents.youtube.default_audio_only')"""
|
184
|
+
keys = path.split('.')
|
185
|
+
current = self.config
|
186
|
+
|
187
|
+
for key in keys:
|
188
|
+
if isinstance(current, dict) and key in current:
|
189
|
+
current = current[key]
|
190
|
+
else:
|
191
|
+
return default
|
192
|
+
|
193
|
+
return current
|
194
|
+
|
195
|
+
def save_sample_config(self, path: str):
|
196
|
+
"""Save a sample configuration file"""
|
197
|
+
try:
|
198
|
+
Path(path).parent.mkdir(parents=True, exist_ok=True)
|
199
|
+
with open(path, 'w') as f:
|
200
|
+
yaml.dump(self.config, f, default_flow_style=False, indent=2)
|
201
|
+
return True
|
202
|
+
except Exception as e:
|
203
|
+
click.echo(f"ā Failed to save sample config: {e}")
|
204
|
+
return False
|
205
|
+
|
206
|
+
|
207
|
+
class AmbivoAgentsCLI:
|
208
|
+
"""Enhanced CLI with agent caching and session management"""
|
209
|
+
|
210
|
+
def __init__(self, config_manager: ConfigManager):
|
211
|
+
self.config = config_manager
|
212
|
+
self.user_id = "cli_user"
|
213
|
+
self.tenant_id = "cli_tenant"
|
214
|
+
self.session_metadata = {
|
215
|
+
"cli_session": True,
|
216
|
+
"version": self.config.get('cli.version', '1.0.0'),
|
217
|
+
"mode": "shell_default"
|
218
|
+
}
|
219
|
+
self.session_file = Path.home() / ".ambivo_agents_session"
|
220
|
+
|
221
|
+
# š Agent caching system
|
222
|
+
self._session_agents: Dict[str, Tuple[Any, Any]] = {} # Cache agents per session
|
223
|
+
self._agent_creation_lock = asyncio.Lock() # Prevent race conditions
|
224
|
+
|
225
|
+
self._ensure_auto_session()
|
226
|
+
|
227
|
+
# Check import status
|
228
|
+
if not AGENT_SESSION_AVAILABLE:
|
229
|
+
if self.config.get('cli.verbose', False):
|
230
|
+
print("ā ļø Warning: AgentSession not available - some features may be limited")
|
231
|
+
print("š” Ensure AgentSession is exported in ambivo_agents/__init__.py")
|
232
|
+
|
233
|
+
def _ensure_auto_session(self):
|
234
|
+
"""Automatically create a session if none exists and auto_session is enabled"""
|
235
|
+
if self.config.get('cli.auto_session', True):
|
236
|
+
current_session = self.get_current_session()
|
237
|
+
if not current_session:
|
238
|
+
# Auto-create a UUID4 session
|
239
|
+
session_id = str(uuid.uuid4())
|
240
|
+
self.set_current_session(session_id)
|
241
|
+
if self.config.get('cli.verbose', False):
|
242
|
+
click.echo(f"š Auto-created session: {session_id}")
|
243
|
+
|
244
|
+
def get_current_session(self) -> Optional[str]:
|
245
|
+
"""Get the currently active session from file"""
|
246
|
+
try:
|
247
|
+
if self.session_file.exists():
|
248
|
+
return self.session_file.read_text().strip()
|
249
|
+
except Exception:
|
250
|
+
pass
|
251
|
+
return None
|
252
|
+
|
253
|
+
def set_current_session(self, session_id: str):
|
254
|
+
"""Set the current session and save to file"""
|
255
|
+
try:
|
256
|
+
self.session_file.write_text(session_id)
|
257
|
+
return True
|
258
|
+
except Exception as e:
|
259
|
+
click.echo(f"ā Failed to save session: {e}")
|
260
|
+
return False
|
261
|
+
|
262
|
+
def clear_current_session(self):
|
263
|
+
"""Clear the current session"""
|
264
|
+
try:
|
265
|
+
if self.session_file.exists():
|
266
|
+
self.session_file.unlink()
|
267
|
+
return True
|
268
|
+
except Exception as e:
|
269
|
+
click.echo(f"ā Failed to clear session: {e}")
|
270
|
+
return False
|
271
|
+
|
272
|
+
async def get_or_create_agent(self, agent_class, session_id: str = None,
|
273
|
+
additional_metadata: Dict[str, Any] = None):
|
274
|
+
"""
|
275
|
+
š Get existing agent from cache or create new one
|
276
|
+
This ensures agent reuse within a session
|
277
|
+
"""
|
278
|
+
if session_id is None:
|
279
|
+
session_id = self.get_current_session()
|
280
|
+
|
281
|
+
if not session_id:
|
282
|
+
raise ValueError("No session ID available for agent creation")
|
283
|
+
|
284
|
+
# Create cache key: agent_class + session_id
|
285
|
+
cache_key = f"{agent_class.__name__}_{session_id}"
|
286
|
+
|
287
|
+
# Use lock to prevent race conditions during agent creation
|
288
|
+
async with self._agent_creation_lock:
|
289
|
+
# Check if agent already exists in cache
|
290
|
+
if cache_key in self._session_agents:
|
291
|
+
agent, context = self._session_agents[cache_key]
|
292
|
+
|
293
|
+
if self.config.get('cli.verbose', False):
|
294
|
+
click.echo(f"š Reusing cached {agent_class.__name__} (ID: {agent.agent_id})")
|
295
|
+
|
296
|
+
return agent, context
|
297
|
+
|
298
|
+
# Create new agent if not in cache
|
299
|
+
if self.config.get('cli.verbose', False):
|
300
|
+
click.echo(f"š Creating new {agent_class.__name__} for session {session_id[:8]}...")
|
301
|
+
|
302
|
+
metadata = {**self.session_metadata}
|
303
|
+
if additional_metadata:
|
304
|
+
metadata.update(additional_metadata)
|
305
|
+
|
306
|
+
metadata['config'] = {
|
307
|
+
'agent_type': agent_class.__name__,
|
308
|
+
'configured': True,
|
309
|
+
'cached': True,
|
310
|
+
'session_id': session_id
|
311
|
+
}
|
312
|
+
|
313
|
+
# š§ FIXED: Use consistent agent_id based on session + agent type
|
314
|
+
consistent_agent_id = f"{agent_class.__name__.lower()}_{session_id}"
|
315
|
+
|
316
|
+
agent, context = agent_class.create(
|
317
|
+
agent_id=consistent_agent_id, # šÆ CONSISTENT ID
|
318
|
+
user_id=self.user_id,
|
319
|
+
tenant_id=self.tenant_id,
|
320
|
+
session_metadata=metadata,
|
321
|
+
session_id=session_id,
|
322
|
+
conversation_id=session_id
|
323
|
+
)
|
324
|
+
|
325
|
+
# Cache the agent
|
326
|
+
self._session_agents[cache_key] = (agent, context)
|
327
|
+
|
328
|
+
if self.config.get('cli.verbose', False):
|
329
|
+
click.echo(f"ā
Cached {agent_class.__name__} (ID: {agent.agent_id})")
|
330
|
+
click.echo(f"š Total cached agents: {len(self._session_agents)}")
|
331
|
+
|
332
|
+
return agent, context
|
333
|
+
|
334
|
+
def clear_session_agents(self, session_id: str = None):
|
335
|
+
"""
|
336
|
+
š Clear cached agents for a specific session
|
337
|
+
Call this when session ends
|
338
|
+
"""
|
339
|
+
if session_id is None:
|
340
|
+
session_id = self.get_current_session()
|
341
|
+
|
342
|
+
if not session_id:
|
343
|
+
return
|
344
|
+
|
345
|
+
keys_to_remove = [key for key in self._session_agents.keys() if key.endswith(session_id)]
|
346
|
+
|
347
|
+
for key in keys_to_remove:
|
348
|
+
agent, context = self._session_agents[key]
|
349
|
+
|
350
|
+
if self.config.get('cli.verbose', False):
|
351
|
+
click.echo(f"šļø Removing cached agent: {agent.agent_id}")
|
352
|
+
|
353
|
+
# Optionally cleanup the agent
|
354
|
+
try:
|
355
|
+
# Note: We can call cleanup_session here since we're ending the session
|
356
|
+
asyncio.create_task(agent.cleanup_session())
|
357
|
+
except Exception as e:
|
358
|
+
click.echo(f"ā ļø Warning during agent cleanup: {e}")
|
359
|
+
|
360
|
+
del self._session_agents[key]
|
361
|
+
|
362
|
+
if keys_to_remove and self.config.get('cli.verbose', False):
|
363
|
+
click.echo(f"š§¹ Cleared {len(keys_to_remove)} agents for session {session_id[:8]}...")
|
364
|
+
|
365
|
+
def clear_all_agents(self):
|
366
|
+
"""
|
367
|
+
š Clear all cached agents (emergency cleanup)
|
368
|
+
"""
|
369
|
+
if self.config.get('cli.verbose', False):
|
370
|
+
click.echo(f"š§¹ Clearing all {len(self._session_agents)} cached agents...")
|
371
|
+
|
372
|
+
for key, (agent, context) in self._session_agents.items():
|
373
|
+
try:
|
374
|
+
asyncio.create_task(agent.cleanup_session())
|
375
|
+
except Exception as e:
|
376
|
+
click.echo(f"ā ļø Warning during agent cleanup: {e}")
|
377
|
+
|
378
|
+
self._session_agents.clear()
|
379
|
+
|
380
|
+
def get_cached_agents_info(self) -> Dict[str, Any]:
|
381
|
+
"""
|
382
|
+
š Get information about cached agents
|
383
|
+
"""
|
384
|
+
info = {
|
385
|
+
'total_agents': len(self._session_agents),
|
386
|
+
'agents': []
|
387
|
+
}
|
388
|
+
|
389
|
+
for key, (agent, context) in self._session_agents.items():
|
390
|
+
agent_info = {
|
391
|
+
'cache_key': key,
|
392
|
+
'agent_id': agent.agent_id,
|
393
|
+
'agent_type': agent.__class__.__name__,
|
394
|
+
'session_id': context.session_id,
|
395
|
+
'created_at': context.created_at.isoformat(),
|
396
|
+
'memory_available': hasattr(agent, 'memory') and agent.memory is not None
|
397
|
+
}
|
398
|
+
info['agents'].append(agent_info)
|
399
|
+
|
400
|
+
return info
|
401
|
+
|
402
|
+
# š§ UPDATED: Replace the old create_agent method
|
403
|
+
async def create_agent(self, agent_class, additional_metadata: Dict[str, Any] = None):
|
404
|
+
"""
|
405
|
+
š§ UPDATED: Now uses caching system
|
406
|
+
Kept for backward compatibility but delegates to get_or_create_agent
|
407
|
+
"""
|
408
|
+
return await self.get_or_create_agent(agent_class, None, additional_metadata)
|
409
|
+
|
410
|
+
async def smart_message_routing(self, message: str) -> str:
|
411
|
+
"""
|
412
|
+
š§ UPDATED: Smart routing with agent caching
|
413
|
+
Now reuses agents instead of creating new ones each time
|
414
|
+
"""
|
415
|
+
message_lower = message.lower()
|
416
|
+
current_session = self.get_current_session()
|
417
|
+
|
418
|
+
if not current_session:
|
419
|
+
raise ValueError("No active session for message processing")
|
420
|
+
|
421
|
+
# š¬ YouTube Download Detection
|
422
|
+
if any(keyword in message_lower for keyword in ['youtube', 'download', 'youtu.be']) and (
|
423
|
+
'http' in message or 'www.' in message):
|
424
|
+
agent, context = await self.get_or_create_agent(YouTubeDownloadAgent, current_session,
|
425
|
+
{"operation": "youtube_download"})
|
426
|
+
|
427
|
+
try:
|
428
|
+
# Extract YouTube URLs
|
429
|
+
import re
|
430
|
+
youtube_patterns = [
|
431
|
+
r'https?://(?:www\.)?youtube\.com/watch\?v=[\w-]+',
|
432
|
+
r'https?://(?:www\.)?youtu\.be/[\w-]+',
|
433
|
+
]
|
434
|
+
|
435
|
+
urls = []
|
436
|
+
for pattern in youtube_patterns:
|
437
|
+
urls.extend(re.findall(pattern, message))
|
438
|
+
|
439
|
+
if urls:
|
440
|
+
url = urls[0] # Use first URL found
|
441
|
+
|
442
|
+
# Use configuration for default behavior
|
443
|
+
default_audio_only = self.config.get('agents.youtube.default_audio_only', True)
|
444
|
+
wants_video = any(keyword in message_lower for keyword in ['video', 'mp4', 'watch', 'visual'])
|
445
|
+
audio_only = default_audio_only if not wants_video else False
|
446
|
+
|
447
|
+
if 'info' in message_lower or 'information' in message_lower:
|
448
|
+
result = await agent._get_youtube_info(url)
|
449
|
+
else:
|
450
|
+
result = await agent._download_youtube(url, audio_only=audio_only)
|
451
|
+
|
452
|
+
# š§ REMOVED: await agent.cleanup_session() - Agent stays cached
|
453
|
+
|
454
|
+
if result['success']:
|
455
|
+
return f"ā
YouTube operation completed!\n{result.get('message', '')}\nSession: {context.session_id}\nAgent: {agent.agent_id}"
|
456
|
+
else:
|
457
|
+
return f"ā YouTube operation failed: {result['error']}"
|
458
|
+
else:
|
459
|
+
return "ā No valid YouTube URLs found in message"
|
460
|
+
|
461
|
+
except Exception as e:
|
462
|
+
return f"ā YouTube operation error: {e}"
|
463
|
+
|
464
|
+
# šµ Media Processing Detection
|
465
|
+
elif any(keyword in message_lower for keyword in
|
466
|
+
['extract audio', 'convert video', 'media', 'ffmpeg', '.mp4', '.avi', '.mov']):
|
467
|
+
agent, context = await self.get_or_create_agent(MediaEditorAgent, current_session,
|
468
|
+
{"operation": "media_processing"})
|
469
|
+
|
470
|
+
try:
|
471
|
+
supported_formats = self.config.get('agents.media.supported_formats', ['mp4', 'avi', 'mov'])
|
472
|
+
|
473
|
+
if 'extract audio' in message_lower:
|
474
|
+
return f"šµ Media Editor Agent ready for audio extraction!\nSupported formats: {', '.join(supported_formats)}\nAgent: {agent.agent_id}\nSession: {context.session_id}"
|
475
|
+
elif 'convert' in message_lower:
|
476
|
+
return f"š„ Media Editor Agent ready for video conversion!\nSupported formats: {', '.join(supported_formats)}\nAgent: {agent.agent_id}"
|
477
|
+
else:
|
478
|
+
return f"š¬ Media Editor Agent ready!\nSupported formats: {', '.join(supported_formats)}\nAgent: {agent.agent_id}"
|
479
|
+
|
480
|
+
except Exception as e:
|
481
|
+
return f"ā Media processing error: {e}"
|
482
|
+
|
483
|
+
# š Knowledge Base Detection
|
484
|
+
elif any(keyword in message_lower for keyword in ['knowledge base', 'ingest', 'query', 'document', 'kb ']):
|
485
|
+
agent, context = await self.get_or_create_agent(KnowledgeBaseAgent, current_session,
|
486
|
+
{"operation": "knowledge_base"})
|
487
|
+
|
488
|
+
try:
|
489
|
+
chunk_size = self.config.get('agents.knowledge_base.chunk_size', 1000)
|
490
|
+
|
491
|
+
if 'ingest' in message_lower:
|
492
|
+
return f"š Knowledge Base Agent ready for document ingestion!\nChunk size: {chunk_size}\nAgent: {agent.agent_id}"
|
493
|
+
elif 'query' in message_lower:
|
494
|
+
return f"š Knowledge Base Agent ready for queries!\nAgent: {agent.agent_id}"
|
495
|
+
else:
|
496
|
+
return f"š Knowledge Base Agent ready!\nAgent: {agent.agent_id}"
|
497
|
+
|
498
|
+
except Exception as e:
|
499
|
+
return f"ā Knowledge base error: {e}"
|
500
|
+
|
501
|
+
# š Web Search Detection
|
502
|
+
elif any(keyword in message_lower for keyword in ['search', 'find', 'look up', 'google']):
|
503
|
+
agent, context = await self.get_or_create_agent(WebSearchAgent, current_session,
|
504
|
+
{"operation": "web_search"})
|
505
|
+
|
506
|
+
try:
|
507
|
+
max_results = self.config.get('agents.web_search.default_max_results', 5)
|
508
|
+
|
509
|
+
# Extract search query
|
510
|
+
search_query = message
|
511
|
+
for prefix in ['search for', 'find', 'look up', 'google']:
|
512
|
+
if prefix in message_lower:
|
513
|
+
search_query = message[message_lower.find(prefix) + len(prefix):].strip()
|
514
|
+
break
|
515
|
+
|
516
|
+
result = await agent._search_web(search_query, max_results=max_results)
|
517
|
+
|
518
|
+
if result['success']:
|
519
|
+
response = f"š Search Results for '{search_query}':\n\n"
|
520
|
+
for i, res in enumerate(result['results'][:3], 1):
|
521
|
+
response += f"{i}. **{res.get('title', 'No title')}**\n"
|
522
|
+
response += f" {res.get('url', 'No URL')}\n"
|
523
|
+
response += f" {res.get('snippet', 'No snippet')[:150]}...\n\n"
|
524
|
+
response += f"Agent: {agent.agent_id}\nSession: {context.session_id}"
|
525
|
+
return response
|
526
|
+
else:
|
527
|
+
return f"ā Search failed: {result['error']}"
|
528
|
+
|
529
|
+
except Exception as e:
|
530
|
+
return f"ā Search error: {e}"
|
531
|
+
|
532
|
+
# š·ļø Web Scraping Detection
|
533
|
+
elif any(keyword in message_lower for keyword in ['scrape', 'extract', 'crawl']) and (
|
534
|
+
'http' in message or 'www.' in message):
|
535
|
+
agent, context = await self.get_or_create_agent(WebScraperAgent, current_session,
|
536
|
+
{"operation": "web_scraping"})
|
537
|
+
|
538
|
+
try:
|
539
|
+
# Extract URLs
|
540
|
+
import re
|
541
|
+
url_pattern = r'https?://[^\s]+'
|
542
|
+
urls = re.findall(url_pattern, message)
|
543
|
+
|
544
|
+
if urls:
|
545
|
+
url = urls[0]
|
546
|
+
user_agent = self.config.get('agents.web_scraper.user_agent', 'Ambivo-Agent/1.0')
|
547
|
+
return f"š·ļø Web Scraper Agent ready to scrape: {url}\nUser-Agent: {user_agent}\nAgent: {agent.agent_id}\nSession: {context.session_id}"
|
548
|
+
else:
|
549
|
+
return "ā No valid URLs found for scraping"
|
550
|
+
|
551
|
+
except Exception as e:
|
552
|
+
return f"ā Web scraping error: {e}"
|
553
|
+
|
554
|
+
# š» Code Execution Detection
|
555
|
+
elif '```' in message:
|
556
|
+
agent, context = await self.get_or_create_agent(CodeExecutorAgent, current_session,
|
557
|
+
{"operation": "code_execution"})
|
558
|
+
|
559
|
+
try:
|
560
|
+
allowed_languages = self.config.get('agents.code_executor.allowed_languages', ['python', 'javascript'])
|
561
|
+
return f"š» Code Executor Agent ready!\nAllowed languages: {', '.join(allowed_languages)}\nAgent: {agent.agent_id}\nSession: {context.session_id}"
|
562
|
+
|
563
|
+
except Exception as e:
|
564
|
+
return f"ā Code execution error: {e}"
|
565
|
+
|
566
|
+
# š¤ General Assistant (fallback) - UPDATED for caching
|
567
|
+
else:
|
568
|
+
agent, context = await self.get_or_create_agent(AssistantAgent, current_session,
|
569
|
+
{"operation": "general_assistance"})
|
570
|
+
|
571
|
+
try:
|
572
|
+
# Create an AgentMessage for the AssistantAgent
|
573
|
+
from ambivo_agents.core.base import AgentMessage, MessageType
|
574
|
+
import uuid
|
575
|
+
|
576
|
+
agent_message = AgentMessage(
|
577
|
+
id=f"msg_{str(uuid.uuid4())[:8]}",
|
578
|
+
sender_id="cli_user",
|
579
|
+
recipient_id=agent.agent_id,
|
580
|
+
content=message,
|
581
|
+
message_type=MessageType.USER_INPUT,
|
582
|
+
session_id=context.session_id,
|
583
|
+
conversation_id=context.conversation_id
|
584
|
+
)
|
585
|
+
|
586
|
+
response_message = await agent.process_message(agent_message, context.to_execution_context())
|
587
|
+
|
588
|
+
|
589
|
+
return response_message.content
|
590
|
+
|
591
|
+
except Exception as e:
|
592
|
+
return f"ā Error processing your question: {e}"
|
593
|
+
|
594
|
+
|
595
|
+
# Initialize configuration and CLI
|
596
|
+
config_manager = None
|
597
|
+
cli_instance = None
|
598
|
+
|
599
|
+
|
600
|
+
def initialize_cli(config_path: Optional[str] = None, verbose: bool = False):
|
601
|
+
"""Initialize CLI with configuration"""
|
602
|
+
global config_manager, cli_instance
|
603
|
+
|
604
|
+
config_manager = ConfigManager(config_path)
|
605
|
+
if verbose:
|
606
|
+
config_manager.config['cli']['verbose'] = True
|
607
|
+
|
608
|
+
cli_instance = AmbivoAgentsCLI(config_manager)
|
609
|
+
return cli_instance
|
610
|
+
|
611
|
+
|
612
|
+
@click.group(invoke_without_command=True)
|
613
|
+
@click.version_option(version="1.0.0", prog_name="Ambivo Agents")
|
614
|
+
@click.option('--config', '-c', help='Configuration file path')
|
615
|
+
@click.option('--verbose', '-v', is_flag=True, help='Enable verbose output')
|
616
|
+
@click.pass_context
|
617
|
+
def cli(ctx, config: Optional[str], verbose: bool):
|
618
|
+
"""
|
619
|
+
Ambivo Agents - Multi-Agent AI System CLI (Shell Mode by Default)
|
620
|
+
|
621
|
+
š Features:
|
622
|
+
- YouTube Downloads with pytubefix
|
623
|
+
- Media Processing with FFmpeg
|
624
|
+
- Knowledge Base Operations with Qdrant
|
625
|
+
- Web Search with multiple providers
|
626
|
+
- Web Scraping with proxy support
|
627
|
+
- Code Execution in Docker containers
|
628
|
+
- YAML Configuration Support
|
629
|
+
- Shell Mode by Default
|
630
|
+
- Auto-Session Creation
|
631
|
+
- Agent Caching & Reuse
|
632
|
+
|
633
|
+
Author: Hemant Gosain 'Sunny'
|
634
|
+
Company: Ambivo
|
635
|
+
Email: sgosain@ambivo.com
|
636
|
+
"""
|
637
|
+
global cli_instance
|
638
|
+
|
639
|
+
# Initialize CLI
|
640
|
+
cli_instance = initialize_cli(config, verbose)
|
641
|
+
|
642
|
+
if verbose:
|
643
|
+
click.echo("š¤ Ambivo Agents CLI v1.0.0 - Shell Mode Default with Agent Caching")
|
644
|
+
click.echo("š§ Contact: sgosain@ambivo.com")
|
645
|
+
click.echo("š¢ Company: https://www.ambivo.com")
|
646
|
+
click.echo("š Agent caching and session management enabled")
|
647
|
+
|
648
|
+
# If no command was provided, start shell mode by default
|
649
|
+
if ctx.invoked_subcommand is None:
|
650
|
+
default_mode = cli_instance.config.get('cli.default_mode', 'shell')
|
651
|
+
if default_mode == 'shell':
|
652
|
+
ctx.invoke(shell)
|
653
|
+
else:
|
654
|
+
click.echo(ctx.get_help())
|
655
|
+
|
656
|
+
|
657
|
+
@cli.group()
|
658
|
+
def config():
|
659
|
+
"""Configuration management commands"""
|
660
|
+
pass
|
661
|
+
|
662
|
+
|
663
|
+
@config.command()
|
664
|
+
def show():
|
665
|
+
"""Show current configuration"""
|
666
|
+
click.echo("š Current Configuration:")
|
667
|
+
click.echo("=" * 50)
|
668
|
+
|
669
|
+
def print_config(data, indent=0):
|
670
|
+
for key, value in data.items():
|
671
|
+
if isinstance(value, dict):
|
672
|
+
click.echo(" " * indent + f"{key}:")
|
673
|
+
print_config(value, indent + 1)
|
674
|
+
else:
|
675
|
+
click.echo(" " * indent + f"{key}: {value}")
|
676
|
+
|
677
|
+
print_config(cli_instance.config.config)
|
678
|
+
|
679
|
+
if cli_instance.config.config_path:
|
680
|
+
click.echo(f"\nš Loaded from: {cli_instance.config.config_path}")
|
681
|
+
else:
|
682
|
+
click.echo(f"\nš Using default configuration")
|
683
|
+
|
684
|
+
|
685
|
+
@config.command()
|
686
|
+
@click.argument('path')
|
687
|
+
def save_sample(path: str):
|
688
|
+
"""Save a sample configuration file"""
|
689
|
+
if cli_instance.config.save_sample_config(path):
|
690
|
+
click.echo(f"ā
Sample configuration saved to: {path}")
|
691
|
+
click.echo("š” Edit the file and use --config to load it")
|
692
|
+
else:
|
693
|
+
click.echo("ā Failed to save sample configuration")
|
694
|
+
|
695
|
+
|
696
|
+
@config.command()
|
697
|
+
@click.argument('key')
|
698
|
+
@click.argument('value')
|
699
|
+
def set(key: str, value: str):
|
700
|
+
"""Set a configuration value (runtime only)"""
|
701
|
+
# Try to parse value as appropriate type
|
702
|
+
try:
|
703
|
+
if value.lower() in ['true', 'false']:
|
704
|
+
parsed_value = value.lower() == 'true'
|
705
|
+
elif value.isdigit():
|
706
|
+
parsed_value = int(value)
|
707
|
+
elif '.' in value and value.replace('.', '').isdigit():
|
708
|
+
parsed_value = float(value)
|
709
|
+
else:
|
710
|
+
parsed_value = value
|
711
|
+
except:
|
712
|
+
parsed_value = value
|
713
|
+
|
714
|
+
# Set the value in current config
|
715
|
+
keys = key.split('.')
|
716
|
+
current = cli_instance.config.config
|
717
|
+
for k in keys[:-1]:
|
718
|
+
if k not in current:
|
719
|
+
current[k] = {}
|
720
|
+
current = current[k]
|
721
|
+
|
722
|
+
current[keys[-1]] = parsed_value
|
723
|
+
click.echo(f"ā
Set {key} = {parsed_value}")
|
724
|
+
click.echo("š” This change is runtime-only. Save to file to persist.")
|
725
|
+
|
726
|
+
|
727
|
+
@config.command()
|
728
|
+
@click.argument('key')
|
729
|
+
def get(key: str):
|
730
|
+
"""Get a configuration value"""
|
731
|
+
value = cli_instance.config.get(key)
|
732
|
+
if value is not None:
|
733
|
+
click.echo(f"{key}: {value}")
|
734
|
+
else:
|
735
|
+
click.echo(f"ā Configuration key '{key}' not found")
|
736
|
+
|
737
|
+
|
738
|
+
@cli.group()
|
739
|
+
def session():
|
740
|
+
"""Session management commands"""
|
741
|
+
pass
|
742
|
+
|
743
|
+
|
744
|
+
@session.command()
|
745
|
+
@click.argument('session_name', required=False)
|
746
|
+
def create(session_name: Optional[str]):
|
747
|
+
"""Create and activate a session (auto-generates UUID4 if no name provided)"""
|
748
|
+
# š Clear any existing cached agents before creating new session
|
749
|
+
if hasattr(cli_instance, '_session_agents'):
|
750
|
+
cli_instance.clear_all_agents()
|
751
|
+
|
752
|
+
if session_name:
|
753
|
+
session_prefix = cli_instance.config.get('cli.session_prefix', 'ambivo')
|
754
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
755
|
+
full_session_id = f"{session_prefix}_{session_name}_{timestamp}"
|
756
|
+
else:
|
757
|
+
# Generate UUID4 session
|
758
|
+
full_session_id = str(uuid.uuid4())
|
759
|
+
|
760
|
+
if cli_instance.set_current_session(full_session_id):
|
761
|
+
click.echo(f"ā
Created and activated session: {full_session_id}")
|
762
|
+
click.echo(f"š” All commands will now use this session automatically")
|
763
|
+
click.echo(f"š§¹ Cleared all cached agents")
|
764
|
+
click.echo(f"š§ Use 'ambivo-agents session end' to deactivate")
|
765
|
+
else:
|
766
|
+
click.echo("ā Failed to create session")
|
767
|
+
sys.exit(1)
|
768
|
+
|
769
|
+
|
770
|
+
@session.command()
|
771
|
+
@click.argument('session_name')
|
772
|
+
def use(session_name: str):
|
773
|
+
"""Switch to an existing session"""
|
774
|
+
# Clear cached agents when switching sessions
|
775
|
+
if hasattr(cli_instance, '_session_agents'):
|
776
|
+
cli_instance.clear_all_agents()
|
777
|
+
|
778
|
+
if cli_instance.set_current_session(session_name):
|
779
|
+
click.echo(f"ā
Switched to session: {session_name}")
|
780
|
+
click.echo(f"š” All commands will now use this session")
|
781
|
+
click.echo(f"š§¹ Cleared cached agents from previous session")
|
782
|
+
else:
|
783
|
+
click.echo("ā Failed to switch session")
|
784
|
+
sys.exit(1)
|
785
|
+
|
786
|
+
|
787
|
+
@session.command()
|
788
|
+
def current():
|
789
|
+
"""Show the currently active session"""
|
790
|
+
current = cli_instance.get_current_session()
|
791
|
+
if current:
|
792
|
+
click.echo(f"š Current session: {current}")
|
793
|
+
|
794
|
+
# Show cached agents info
|
795
|
+
if hasattr(cli_instance, '_session_agents'):
|
796
|
+
agents_info = cli_instance.get_cached_agents_info()
|
797
|
+
click.echo(f"š¤ Cached agents: {agents_info['total_agents']}")
|
798
|
+
else:
|
799
|
+
click.echo("ā No active session")
|
800
|
+
click.echo("š” Create one with: ambivo-agents session create my_session")
|
801
|
+
|
802
|
+
|
803
|
+
@session.command()
|
804
|
+
def end():
|
805
|
+
"""End the current session"""
|
806
|
+
current = cli_instance.get_current_session()
|
807
|
+
if current:
|
808
|
+
# š Clear cached agents for this session
|
809
|
+
if hasattr(cli_instance, '_session_agents'):
|
810
|
+
cli_instance.clear_session_agents(current)
|
811
|
+
|
812
|
+
if cli_instance.clear_current_session():
|
813
|
+
click.echo(f"ā
Ended session: {current}")
|
814
|
+
click.echo(f"š§¹ Cleared cached agents for session")
|
815
|
+
|
816
|
+
# Auto-create a replacement session if auto_session is enabled
|
817
|
+
cli_instance._ensure_auto_session()
|
818
|
+
new_session = cli_instance.get_current_session()
|
819
|
+
if new_session:
|
820
|
+
click.echo(f"š Auto-created replacement session: {new_session}")
|
821
|
+
else:
|
822
|
+
click.echo("ā Failed to end session")
|
823
|
+
sys.exit(1)
|
824
|
+
else:
|
825
|
+
click.echo("ā No active session to end")
|
826
|
+
|
827
|
+
|
828
|
+
@session.command()
|
829
|
+
@click.option('--limit', '-l', default=20, help='Maximum number of messages to show')
|
830
|
+
@click.option('--format', '-f', type=click.Choice(['text', 'json']), default='text', help='Output format')
|
831
|
+
def history(limit: int, format: str):
|
832
|
+
"""Show conversation history for the current session"""
|
833
|
+
|
834
|
+
# Try to get the current session from shell mode first
|
835
|
+
try:
|
836
|
+
from pathlib import Path
|
837
|
+
session_file = Path.home() / '.ambivo' / 'current_session'
|
838
|
+
if session_file.exists():
|
839
|
+
current_session = session_file.read_text().strip()
|
840
|
+
click.echo(f"š Debug: Using shell session: {current_session}")
|
841
|
+
else:
|
842
|
+
current_session = None
|
843
|
+
click.echo(f"š Debug: No shell session file found")
|
844
|
+
except Exception as e:
|
845
|
+
current_session = None
|
846
|
+
click.echo(f"š Debug: Error reading shell session: {e}")
|
847
|
+
|
848
|
+
if not current_session:
|
849
|
+
click.echo("ā No active session")
|
850
|
+
click.echo("š” Create a session with: ambivo-agents session create my_session")
|
851
|
+
click.echo("š” Or start shell mode: ambivo-agents")
|
852
|
+
return
|
853
|
+
|
854
|
+
async def show_history():
|
855
|
+
try:
|
856
|
+
# š§ UPDATED: Use cached agent if available
|
857
|
+
agent, context = await cli_instance.get_or_create_agent(AssistantAgent, current_session, {
|
858
|
+
"operation": "history_access",
|
859
|
+
"use_session": current_session
|
860
|
+
})
|
861
|
+
|
862
|
+
# Override the context to use the shell session
|
863
|
+
agent.context.session_id = current_session
|
864
|
+
agent.context.conversation_id = current_session
|
865
|
+
|
866
|
+
# Get conversation history
|
867
|
+
history_data = await agent.get_conversation_history(limit=limit, include_metadata=True)
|
868
|
+
|
869
|
+
if not history_data:
|
870
|
+
click.echo(f"š No conversation history found for session: {current_session}")
|
871
|
+
return
|
872
|
+
|
873
|
+
if format == 'json':
|
874
|
+
click.echo(json.dumps(history_data, indent=2, default=str))
|
875
|
+
else:
|
876
|
+
session_display = current_session[:12] + "..." if len(current_session) > 12 else current_session
|
877
|
+
click.echo(f"š **Conversation History** (Session: {session_display})")
|
878
|
+
click.echo(f"š Total Messages: {len(history_data)}")
|
879
|
+
click.echo("=" * 60)
|
880
|
+
|
881
|
+
for i, msg in enumerate(history_data, 1):
|
882
|
+
timestamp = msg.get('timestamp', 'Unknown time')
|
883
|
+
if isinstance(timestamp, str):
|
884
|
+
try:
|
885
|
+
from datetime import datetime
|
886
|
+
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
|
887
|
+
timestamp = dt.strftime('%Y-%m-%d %H:%M:%S')
|
888
|
+
except:
|
889
|
+
pass
|
890
|
+
|
891
|
+
message_type = msg.get('message_type', 'unknown')
|
892
|
+
content = msg.get('content', '')
|
893
|
+
sender = msg.get('sender_id', 'unknown')
|
894
|
+
|
895
|
+
# Format based on message type
|
896
|
+
if message_type == 'user_input':
|
897
|
+
icon = "š£ļø "
|
898
|
+
label = "You"
|
899
|
+
elif message_type == 'agent_response':
|
900
|
+
icon = "š¤"
|
901
|
+
label = f"Agent ({sender})"
|
902
|
+
else:
|
903
|
+
icon = "ā¹ļø "
|
904
|
+
label = message_type.title()
|
905
|
+
|
906
|
+
click.echo(f"\n{i}. {icon} **{label}** - {timestamp}")
|
907
|
+
click.echo(f" {content}")
|
908
|
+
|
909
|
+
if i < len(history_data):
|
910
|
+
click.echo(" " + "-" * 50)
|
911
|
+
|
912
|
+
except Exception as e:
|
913
|
+
click.echo(f"ā Error retrieving history: {e}")
|
914
|
+
import traceback
|
915
|
+
click.echo(f"š Debug traceback: {traceback.format_exc()}")
|
916
|
+
|
917
|
+
asyncio.run(show_history())
|
918
|
+
|
919
|
+
|
920
|
+
@session.command()
|
921
|
+
@click.option('--format', '-f', type=click.Choice(['text', 'json']), default='text', help='Output format')
|
922
|
+
def summary(format: str):
|
923
|
+
"""Show summary of the current session"""
|
924
|
+
current_session = cli_instance.get_current_session()
|
925
|
+
|
926
|
+
if not current_session:
|
927
|
+
click.echo("ā No active session")
|
928
|
+
click.echo("š” Create a session with: ambivo-agents session create my_session")
|
929
|
+
return
|
930
|
+
|
931
|
+
async def show_summary():
|
932
|
+
try:
|
933
|
+
# š§ UPDATED: Use cached agent if available
|
934
|
+
agent, context = await cli_instance.get_or_create_agent(AssistantAgent, current_session,
|
935
|
+
{"operation": "summary_access"})
|
936
|
+
|
937
|
+
# Get conversation summary
|
938
|
+
summary_data = await agent.get_conversation_summary()
|
939
|
+
|
940
|
+
if format == 'json':
|
941
|
+
click.echo(json.dumps(summary_data, indent=2, default=str))
|
942
|
+
else:
|
943
|
+
if 'error' in summary_data:
|
944
|
+
click.echo(f"ā Error getting summary: {summary_data['error']}")
|
945
|
+
else:
|
946
|
+
click.echo(f"š **Session Summary**")
|
947
|
+
click.echo("=" * 40)
|
948
|
+
click.echo(f"š Session ID: {summary_data.get('session_id', 'Unknown')}")
|
949
|
+
click.echo(f"š¤ User ID: {summary_data.get('user_id', 'Unknown')}")
|
950
|
+
click.echo(f"š¬ Total Messages: {summary_data.get('total_messages', 0)}")
|
951
|
+
click.echo(f"š£ļø User Messages: {summary_data.get('user_messages', 0)}")
|
952
|
+
click.echo(f"š¤ Agent Messages: {summary_data.get('agent_messages', 0)}")
|
953
|
+
click.echo(f"ā±ļø Session Duration: {summary_data.get('session_duration', 'Unknown')}")
|
954
|
+
|
955
|
+
if summary_data.get('first_message'):
|
956
|
+
click.echo(f"\nš First Message:")
|
957
|
+
click.echo(f" {summary_data['first_message']}")
|
958
|
+
|
959
|
+
if summary_data.get('last_message'):
|
960
|
+
click.echo(f"\nš Last Message:")
|
961
|
+
click.echo(f" {summary_data['last_message']}")
|
962
|
+
|
963
|
+
except Exception as e:
|
964
|
+
click.echo(f"ā Error retrieving summary: {e}")
|
965
|
+
|
966
|
+
asyncio.run(show_summary())
|
967
|
+
|
968
|
+
|
969
|
+
@session.command()
|
970
|
+
@click.confirmation_option(prompt='Are you sure you want to clear the conversation history?')
|
971
|
+
def clear():
|
972
|
+
"""Clear conversation history for the current session"""
|
973
|
+
current_session = cli_instance.get_current_session()
|
974
|
+
|
975
|
+
if not current_session:
|
976
|
+
click.echo("ā No active session")
|
977
|
+
return
|
978
|
+
|
979
|
+
async def clear_history():
|
980
|
+
try:
|
981
|
+
# š§ UPDATED: Use cached agent if available
|
982
|
+
agent, context = await cli_instance.get_or_create_agent(AssistantAgent, current_session,
|
983
|
+
{"operation": "history_clear"})
|
984
|
+
|
985
|
+
# Clear conversation history
|
986
|
+
success = await agent.clear_conversation_history()
|
987
|
+
|
988
|
+
if success:
|
989
|
+
click.echo(f"ā
Cleared conversation history for session: {current_session}")
|
990
|
+
else:
|
991
|
+
click.echo(f"ā Failed to clear conversation history")
|
992
|
+
|
993
|
+
except Exception as e:
|
994
|
+
click.echo(f"ā Error clearing history: {e}")
|
995
|
+
|
996
|
+
asyncio.run(clear_history())
|
997
|
+
|
998
|
+
|
999
|
+
@session.command()
|
1000
|
+
def agents():
|
1001
|
+
"""š Show cached agent information"""
|
1002
|
+
current_session = cli_instance.get_current_session()
|
1003
|
+
|
1004
|
+
if not current_session:
|
1005
|
+
click.echo("ā No active session")
|
1006
|
+
return
|
1007
|
+
|
1008
|
+
agents_info = cli_instance.get_cached_agents_info()
|
1009
|
+
|
1010
|
+
click.echo(f"š¤ **Cached Agents** (Session: {current_session[:8]}...)")
|
1011
|
+
click.echo(f"š Total: {agents_info['total_agents']}")
|
1012
|
+
click.echo("=" * 50)
|
1013
|
+
|
1014
|
+
if agents_info['agents']:
|
1015
|
+
for agent_info in agents_info['agents']:
|
1016
|
+
click.echo(f"\nš **{agent_info['agent_type']}**")
|
1017
|
+
click.echo(f" ID: {agent_info['agent_id']}")
|
1018
|
+
click.echo(f" Session: {agent_info['session_id'][:8]}...")
|
1019
|
+
click.echo(f" Memory: {'ā
' if agent_info['memory_available'] else 'ā'}")
|
1020
|
+
click.echo(f" Created: {agent_info['created_at']}")
|
1021
|
+
click.echo(f" Cache Key: {agent_info['cache_key']}")
|
1022
|
+
else:
|
1023
|
+
click.echo("\nš No cached agents")
|
1024
|
+
click.echo("š” Agents will be created when you send messages")
|
1025
|
+
|
1026
|
+
|
1027
|
+
@cli.command()
|
1028
|
+
def shell():
|
1029
|
+
"""Start Ambivo Agents interactive shell (default mode)"""
|
1030
|
+
|
1031
|
+
# Show welcome message with configuration info
|
1032
|
+
click.echo("š Ambivo Agents Shell v1.0.0 (Default Mode)")
|
1033
|
+
click.echo("š” YAML configuration support with auto-sessions and agent caching")
|
1034
|
+
|
1035
|
+
if cli_instance.config.config_path:
|
1036
|
+
click.echo(f"š Config: {cli_instance.config.config_path}")
|
1037
|
+
else:
|
1038
|
+
click.echo("š Config: Using defaults")
|
1039
|
+
|
1040
|
+
# Show current session
|
1041
|
+
current_session = cli_instance.get_current_session()
|
1042
|
+
if current_session:
|
1043
|
+
session_display = current_session[:8] + "..." if len(current_session) > 8 else current_session
|
1044
|
+
click.echo(f"š Session: {session_display}")
|
1045
|
+
|
1046
|
+
# Show cached agents
|
1047
|
+
if hasattr(cli_instance, '_session_agents'):
|
1048
|
+
agents_info = cli_instance.get_cached_agents_info()
|
1049
|
+
if agents_info['total_agents'] > 0:
|
1050
|
+
click.echo(f"š¤ Cached agents: {agents_info['total_agents']}")
|
1051
|
+
|
1052
|
+
click.echo("š” Type 'help' for commands, 'exit' to quit")
|
1053
|
+
click.echo("-" * 60)
|
1054
|
+
|
1055
|
+
def get_prompt():
|
1056
|
+
"""Generate dynamic prompt based on session state and theme"""
|
1057
|
+
current_session = cli_instance.get_current_session()
|
1058
|
+
theme = cli_instance.config.get('cli.theme', 'default')
|
1059
|
+
|
1060
|
+
if current_session:
|
1061
|
+
# Show shortened session ID in prompt
|
1062
|
+
session_short = current_session[:8] if len(current_session) > 8 else current_session
|
1063
|
+
if theme == 'minimal':
|
1064
|
+
return f"({session_short})> "
|
1065
|
+
else:
|
1066
|
+
return f"ambivo-agents ({session_short})> "
|
1067
|
+
else:
|
1068
|
+
if theme == 'minimal':
|
1069
|
+
return "> "
|
1070
|
+
else:
|
1071
|
+
return "ambivo-agents> "
|
1072
|
+
|
1073
|
+
def process_shell_command(command_line: str):
|
1074
|
+
"""Process a command line in shell mode"""
|
1075
|
+
if not command_line.strip():
|
1076
|
+
return True
|
1077
|
+
|
1078
|
+
# Clean up command line - remove leading colons and extra whitespace
|
1079
|
+
cleaned_command = command_line.strip()
|
1080
|
+
if cleaned_command.startswith(':'):
|
1081
|
+
cleaned_command = cleaned_command[1:].strip()
|
1082
|
+
|
1083
|
+
# Parse command line
|
1084
|
+
parts = cleaned_command.split()
|
1085
|
+
if not parts:
|
1086
|
+
return True
|
1087
|
+
|
1088
|
+
cmd = parts[0].lower()
|
1089
|
+
args = parts[1:] if len(parts) > 1 else []
|
1090
|
+
|
1091
|
+
# Handle shell-specific commands
|
1092
|
+
if cmd in ['exit', 'quit', 'bye']:
|
1093
|
+
click.echo("š Goodbye!")
|
1094
|
+
return False
|
1095
|
+
|
1096
|
+
elif cmd == 'help':
|
1097
|
+
click.echo("""
|
1098
|
+
š Ambivo Agents Shell Commands:
|
1099
|
+
|
1100
|
+
š **Configuration:**
|
1101
|
+
config show - Show current configuration
|
1102
|
+
config get <key> - Get configuration value
|
1103
|
+
config set <key> <value> - Set configuration value (runtime)
|
1104
|
+
config save-sample <path> - Save sample config file
|
1105
|
+
|
1106
|
+
š **Session Management:**
|
1107
|
+
session create [name] - Create session (UUID4 if no name)
|
1108
|
+
session current - Show current session
|
1109
|
+
session status - Full session info
|
1110
|
+
session use <name> - Switch to session
|
1111
|
+
session end - End current session
|
1112
|
+
session history - Show conversation history
|
1113
|
+
session summary - Show session summary
|
1114
|
+
session clear - Clear conversation history
|
1115
|
+
session agents - Show cached agents
|
1116
|
+
|
1117
|
+
š¬ **Chat Commands:**
|
1118
|
+
chat <message> - Send message (uses active session)
|
1119
|
+
<message> - Direct message (shortcut)
|
1120
|
+
|
1121
|
+
š¬ **YouTube Commands:**
|
1122
|
+
youtube download <url> - Download video/audio (config-aware)
|
1123
|
+
youtube info <url> - Get video information
|
1124
|
+
|
1125
|
+
š¤ **Agent Management:**
|
1126
|
+
agents - Show cached agents
|
1127
|
+
debug agents - Debug agent memory status
|
1128
|
+
|
1129
|
+
š **Modes:**
|
1130
|
+
interactive - Start chat-only interactive mode
|
1131
|
+
shell - This shell mode (default)
|
1132
|
+
|
1133
|
+
š ļø **Utilities:**
|
1134
|
+
health - System health check
|
1135
|
+
demo - Run demo
|
1136
|
+
examples - Show usage examples
|
1137
|
+
|
1138
|
+
šŖ **Exit:**
|
1139
|
+
exit, quit, bye - Exit shell
|
1140
|
+
|
1141
|
+
š” **Features:**
|
1142
|
+
- Auto-session creation with UUID4
|
1143
|
+
- agent_config.yaml support
|
1144
|
+
- Configuration-aware agents
|
1145
|
+
- Agent caching and reuse
|
1146
|
+
- Persistent conversation history
|
1147
|
+
- Customizable themes and behavior
|
1148
|
+
""")
|
1149
|
+
return True
|
1150
|
+
|
1151
|
+
elif cmd == 'clear':
|
1152
|
+
click.clear()
|
1153
|
+
return True
|
1154
|
+
|
1155
|
+
# Handle configuration commands
|
1156
|
+
elif cmd == 'config':
|
1157
|
+
return handle_config_command(args)
|
1158
|
+
|
1159
|
+
# Handle regular commands by routing to appropriate CLI commands
|
1160
|
+
try:
|
1161
|
+
if cmd == 'session':
|
1162
|
+
return handle_session_command(args)
|
1163
|
+
elif cmd == 'chat':
|
1164
|
+
return handle_chat_command(args)
|
1165
|
+
elif cmd == 'youtube':
|
1166
|
+
return handle_youtube_command(args)
|
1167
|
+
elif cmd == 'interactive':
|
1168
|
+
return handle_interactive_command()
|
1169
|
+
elif cmd == 'health':
|
1170
|
+
return handle_health_command()
|
1171
|
+
elif cmd == 'demo':
|
1172
|
+
return handle_demo_command()
|
1173
|
+
elif cmd == 'examples':
|
1174
|
+
return handle_examples_command()
|
1175
|
+
elif cmd == 'agents':
|
1176
|
+
return handle_agents_command()
|
1177
|
+
elif cmd == 'debug':
|
1178
|
+
if args and args[0] == 'agents':
|
1179
|
+
return handle_debug_agents_command()
|
1180
|
+
else:
|
1181
|
+
click.echo("ā Available debug commands: debug agents")
|
1182
|
+
return True
|
1183
|
+
else:
|
1184
|
+
# Try to interpret as chat message
|
1185
|
+
return handle_chat_command([command_line])
|
1186
|
+
|
1187
|
+
except Exception as e:
|
1188
|
+
click.echo(f"ā Error executing command: {e}")
|
1189
|
+
return True
|
1190
|
+
|
1191
|
+
def handle_config_command(args):
|
1192
|
+
"""Handle configuration commands in shell"""
|
1193
|
+
if not args:
|
1194
|
+
click.echo("ā Usage: config <show|get|set|save-sample> [args]")
|
1195
|
+
return True
|
1196
|
+
|
1197
|
+
subcmd = args[0].lower()
|
1198
|
+
|
1199
|
+
if subcmd == 'show':
|
1200
|
+
click.echo("š Current Configuration (Key Settings):")
|
1201
|
+
key_settings = [
|
1202
|
+
'cli.default_mode',
|
1203
|
+
'cli.auto_session',
|
1204
|
+
'cli.theme',
|
1205
|
+
'agents.youtube.default_audio_only',
|
1206
|
+
'agents.web_search.default_max_results',
|
1207
|
+
'session.auto_cleanup'
|
1208
|
+
]
|
1209
|
+
for key in key_settings:
|
1210
|
+
value = cli_instance.config.get(key)
|
1211
|
+
click.echo(f" {key}: {value}")
|
1212
|
+
|
1213
|
+
elif subcmd == 'get':
|
1214
|
+
if len(args) < 2:
|
1215
|
+
click.echo("ā Usage: config get <key>")
|
1216
|
+
return True
|
1217
|
+
key = args[1]
|
1218
|
+
value = cli_instance.config.get(key)
|
1219
|
+
if value is not None:
|
1220
|
+
click.echo(f"{key}: {value}")
|
1221
|
+
else:
|
1222
|
+
click.echo(f"ā Configuration key '{key}' not found")
|
1223
|
+
|
1224
|
+
elif subcmd == 'set':
|
1225
|
+
if len(args) < 3:
|
1226
|
+
click.echo("ā Usage: config set <key> <value>")
|
1227
|
+
return True
|
1228
|
+
key = args[1]
|
1229
|
+
value = args[2]
|
1230
|
+
|
1231
|
+
# Parse value
|
1232
|
+
try:
|
1233
|
+
if value.lower() in ['true', 'false']:
|
1234
|
+
parsed_value = value.lower() == 'true'
|
1235
|
+
elif value.isdigit():
|
1236
|
+
parsed_value = int(value)
|
1237
|
+
elif '.' in value and value.replace('.', '').isdigit():
|
1238
|
+
parsed_value = float(value)
|
1239
|
+
else:
|
1240
|
+
parsed_value = value
|
1241
|
+
except:
|
1242
|
+
parsed_value = value
|
1243
|
+
|
1244
|
+
# Set the value
|
1245
|
+
keys = key.split('.')
|
1246
|
+
current = cli_instance.config.config
|
1247
|
+
for k in keys[:-1]:
|
1248
|
+
if k not in current:
|
1249
|
+
current[k] = {}
|
1250
|
+
current = current[k]
|
1251
|
+
|
1252
|
+
current[keys[-1]] = parsed_value
|
1253
|
+
click.echo(f"ā
Set {key} = {parsed_value}")
|
1254
|
+
|
1255
|
+
elif subcmd == 'save-sample':
|
1256
|
+
if len(args) < 2:
|
1257
|
+
path = "./agent_config.yaml"
|
1258
|
+
else:
|
1259
|
+
path = args[1]
|
1260
|
+
|
1261
|
+
if cli_instance.config.save_sample_config(path):
|
1262
|
+
click.echo(f"ā
Sample configuration saved to: {path}")
|
1263
|
+
else:
|
1264
|
+
click.echo("ā Failed to save sample configuration")
|
1265
|
+
|
1266
|
+
else:
|
1267
|
+
click.echo(f"ā Unknown config command: {subcmd}")
|
1268
|
+
|
1269
|
+
return True
|
1270
|
+
|
1271
|
+
# Helper functions for session commands
|
1272
|
+
def handle_session_history(limit: int = 20):
|
1273
|
+
"""Handle session history command with database debugging"""
|
1274
|
+
|
1275
|
+
async def show_history():
|
1276
|
+
current_session = cli_instance.get_current_session()
|
1277
|
+
|
1278
|
+
if not current_session:
|
1279
|
+
click.echo("ā No active session")
|
1280
|
+
return
|
1281
|
+
|
1282
|
+
try:
|
1283
|
+
# š§ UPDATED: Use cached agent
|
1284
|
+
agent, context = await cli_instance.get_or_create_agent(AssistantAgent, current_session,
|
1285
|
+
{"operation": "history_access"})
|
1286
|
+
|
1287
|
+
# Show database info if verbose
|
1288
|
+
if cli_instance.config.get('cli.verbose', False):
|
1289
|
+
if agent.memory and hasattr(agent.memory, 'redis_client'):
|
1290
|
+
redis_client = agent.memory.redis_client
|
1291
|
+
current_db = redis_client.connection_pool.connection_kwargs.get('db', 'Unknown')
|
1292
|
+
expected_key = f"session:{context.conversation_id}:messages"
|
1293
|
+
|
1294
|
+
click.echo(f"š History Database: {current_db}")
|
1295
|
+
click.echo(f"š History Key: {expected_key}")
|
1296
|
+
|
1297
|
+
# Check key existence and length
|
1298
|
+
exists = redis_client.exists(expected_key)
|
1299
|
+
length = redis_client.llen(expected_key) if exists else 0
|
1300
|
+
click.echo(f"š Key exists: {'ā
' if exists else 'ā'}")
|
1301
|
+
click.echo(f"š Key length: {length}")
|
1302
|
+
|
1303
|
+
# Get conversation history
|
1304
|
+
history_data = await agent.get_conversation_history(limit=limit, include_metadata=True)
|
1305
|
+
|
1306
|
+
if not history_data:
|
1307
|
+
click.echo(f"š No conversation history found for current session")
|
1308
|
+
|
1309
|
+
# Enhanced debug for empty history
|
1310
|
+
if cli_instance.config.get('cli.verbose', False) and agent.memory:
|
1311
|
+
click.echo(f"\nš **Debugging Empty History**")
|
1312
|
+
|
1313
|
+
# Test retrieval directly
|
1314
|
+
try:
|
1315
|
+
direct_messages = agent.memory.get_recent_messages(limit=10,
|
1316
|
+
conversation_id=context.conversation_id)
|
1317
|
+
click.echo(f"Direct get_recent_messages: {len(direct_messages)} messages")
|
1318
|
+
|
1319
|
+
if direct_messages:
|
1320
|
+
click.echo(f"Sample direct message:")
|
1321
|
+
sample = direct_messages[0]
|
1322
|
+
if isinstance(sample, dict):
|
1323
|
+
click.echo(f" Content: {sample.get('content', 'No content')[:50]}...")
|
1324
|
+
click.echo(f" Type: {sample.get('message_type', 'No type')}")
|
1325
|
+
except Exception as e:
|
1326
|
+
click.echo(f"Direct retrieval error: {e}")
|
1327
|
+
|
1328
|
+
return
|
1329
|
+
|
1330
|
+
# Display history (rest stays the same)
|
1331
|
+
session_display = current_session[:12] + "..." if len(current_session) > 12 else current_session
|
1332
|
+
click.echo(f"š **Conversation History** (Session: {session_display})")
|
1333
|
+
click.echo(f"š Total Messages: {len(history_data)}")
|
1334
|
+
click.echo("=" * 50)
|
1335
|
+
|
1336
|
+
recent_messages = history_data[-10:] if len(history_data) > 10 else history_data
|
1337
|
+
|
1338
|
+
for i, msg in enumerate(recent_messages, 1):
|
1339
|
+
content = msg.get('content', '')
|
1340
|
+
if len(content) > 100:
|
1341
|
+
content = content[:100] + "..."
|
1342
|
+
|
1343
|
+
message_type = msg.get('message_type', 'unknown')
|
1344
|
+
timestamp = msg.get('timestamp', 'Unknown time')
|
1345
|
+
|
1346
|
+
if isinstance(timestamp, str):
|
1347
|
+
try:
|
1348
|
+
from datetime import datetime
|
1349
|
+
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
|
1350
|
+
timestamp = dt.strftime('%H:%M:%S')
|
1351
|
+
except:
|
1352
|
+
timestamp = str(timestamp)[:8]
|
1353
|
+
|
1354
|
+
if message_type == 'user_input':
|
1355
|
+
click.echo(f"{i}. š£ļø You ({timestamp}): {content}")
|
1356
|
+
elif message_type == 'agent_response':
|
1357
|
+
sender = msg.get('sender_id', 'agent')[:10]
|
1358
|
+
click.echo(f"{i}. š¤ {sender} ({timestamp}): {content}")
|
1359
|
+
else:
|
1360
|
+
click.echo(f"{i}. ā¹ļø {message_type} ({timestamp}): {content}")
|
1361
|
+
|
1362
|
+
if len(history_data) > 10:
|
1363
|
+
click.echo(f"\nš” Showing last 10 of {len(history_data)} messages")
|
1364
|
+
|
1365
|
+
except Exception as e:
|
1366
|
+
click.echo(f"ā Error retrieving history: {e}")
|
1367
|
+
if cli_instance.config.get('cli.verbose', False):
|
1368
|
+
import traceback
|
1369
|
+
click.echo(f"š Debug traceback: {traceback.format_exc()}")
|
1370
|
+
|
1371
|
+
asyncio.run(show_history())
|
1372
|
+
return True
|
1373
|
+
|
1374
|
+
def handle_session_summary():
|
1375
|
+
"""Handle session summary command in shell"""
|
1376
|
+
|
1377
|
+
async def show_summary():
|
1378
|
+
current_session = cli_instance.get_current_session()
|
1379
|
+
|
1380
|
+
if not current_session:
|
1381
|
+
click.echo("ā No active session")
|
1382
|
+
return
|
1383
|
+
|
1384
|
+
try:
|
1385
|
+
# š§ UPDATED: Use cached agent
|
1386
|
+
agent, context = await cli_instance.get_or_create_agent(AssistantAgent, current_session,
|
1387
|
+
{"operation": "summary_access"})
|
1388
|
+
|
1389
|
+
# Get conversation summary
|
1390
|
+
summary_data = await agent.get_conversation_summary()
|
1391
|
+
|
1392
|
+
if 'error' in summary_data:
|
1393
|
+
click.echo(f"ā Error getting summary: {summary_data['error']}")
|
1394
|
+
else:
|
1395
|
+
click.echo(f"š **Session Summary**")
|
1396
|
+
click.echo("=" * 30)
|
1397
|
+
click.echo(f"š¬ Messages: {summary_data.get('total_messages', 0)} total")
|
1398
|
+
click.echo(f"ā±ļø Duration: {summary_data.get('session_duration', 'Unknown')}")
|
1399
|
+
click.echo(f"š Session: {current_session[:8]}...")
|
1400
|
+
|
1401
|
+
except Exception as e:
|
1402
|
+
click.echo(f"ā Error retrieving summary: {e}")
|
1403
|
+
|
1404
|
+
asyncio.run(show_summary())
|
1405
|
+
return True
|
1406
|
+
|
1407
|
+
def handle_session_clear():
|
1408
|
+
"""Handle session clear command in shell"""
|
1409
|
+
|
1410
|
+
async def clear_history():
|
1411
|
+
current_session = cli_instance.get_current_session()
|
1412
|
+
|
1413
|
+
if not current_session:
|
1414
|
+
click.echo("ā No active session")
|
1415
|
+
return
|
1416
|
+
|
1417
|
+
try:
|
1418
|
+
# š§ UPDATED: Use cached agent
|
1419
|
+
agent, context = await cli_instance.get_or_create_agent(AssistantAgent, current_session,
|
1420
|
+
{"operation": "history_clear"})
|
1421
|
+
|
1422
|
+
# Clear conversation history
|
1423
|
+
success = await agent.clear_conversation_history()
|
1424
|
+
|
1425
|
+
if success:
|
1426
|
+
click.echo(f"ā
Cleared conversation history")
|
1427
|
+
else:
|
1428
|
+
click.echo(f"ā Failed to clear conversation history")
|
1429
|
+
|
1430
|
+
except Exception as e:
|
1431
|
+
click.echo(f"ā Error clearing history: {e}")
|
1432
|
+
|
1433
|
+
asyncio.run(clear_history())
|
1434
|
+
return True
|
1435
|
+
|
1436
|
+
def handle_session_command(args):
|
1437
|
+
"""š§ UPDATED: Handle session subcommands with agent cleanup"""
|
1438
|
+
if not args:
|
1439
|
+
click.echo("ā Usage: session <create|current|status|use|end|history|summary|clear|agents> [args]")
|
1440
|
+
return True
|
1441
|
+
|
1442
|
+
subcmd = args[0].lower()
|
1443
|
+
|
1444
|
+
if subcmd == 'create':
|
1445
|
+
# Clear any existing cached agents before creating new session
|
1446
|
+
cli_instance.clear_all_agents()
|
1447
|
+
|
1448
|
+
if len(args) > 1:
|
1449
|
+
session_name = args[1]
|
1450
|
+
session_prefix = cli_instance.config.get('cli.session_prefix', 'ambivo')
|
1451
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
1452
|
+
full_session_id = f"{session_prefix}_{session_name}_{timestamp}"
|
1453
|
+
else:
|
1454
|
+
# Generate UUID4 session
|
1455
|
+
full_session_id = str(uuid.uuid4())
|
1456
|
+
|
1457
|
+
if cli_instance.set_current_session(full_session_id):
|
1458
|
+
click.echo(f"ā
Created and activated session: {full_session_id}")
|
1459
|
+
click.echo(f"š§¹ Cleared all cached agents")
|
1460
|
+
else:
|
1461
|
+
click.echo("ā Failed to create session")
|
1462
|
+
|
1463
|
+
elif subcmd == 'current':
|
1464
|
+
current = cli_instance.get_current_session()
|
1465
|
+
if current:
|
1466
|
+
click.echo(f"š Current session: {current}")
|
1467
|
+
|
1468
|
+
# Show cached agents info
|
1469
|
+
agents_info = cli_instance.get_cached_agents_info()
|
1470
|
+
click.echo(f"š¤ Cached agents: {agents_info['total_agents']}")
|
1471
|
+
else:
|
1472
|
+
click.echo("ā No active session")
|
1473
|
+
|
1474
|
+
elif subcmd == 'status':
|
1475
|
+
current = cli_instance.get_current_session()
|
1476
|
+
auto_session = cli_instance.config.get('cli.auto_session', True)
|
1477
|
+
agents_info = cli_instance.get_cached_agents_info()
|
1478
|
+
|
1479
|
+
click.echo("š Session Status:")
|
1480
|
+
if current:
|
1481
|
+
click.echo(f" ā
Active session: {current}")
|
1482
|
+
click.echo(f" š Session file: {cli_instance.session_file}")
|
1483
|
+
else:
|
1484
|
+
click.echo(" ā No active session")
|
1485
|
+
click.echo(f" š Auto-session: {auto_session}")
|
1486
|
+
click.echo(f" š¤ Cached agents: {agents_info['total_agents']}")
|
1487
|
+
|
1488
|
+
elif subcmd == 'use':
|
1489
|
+
if len(args) < 2:
|
1490
|
+
click.echo("ā Usage: session use <name>")
|
1491
|
+
return True
|
1492
|
+
session_name = args[1]
|
1493
|
+
|
1494
|
+
# Clear cached agents when switching
|
1495
|
+
cli_instance.clear_all_agents()
|
1496
|
+
|
1497
|
+
if cli_instance.set_current_session(session_name):
|
1498
|
+
click.echo(f"ā
Switched to session: {session_name}")
|
1499
|
+
click.echo(f"š§¹ Cleared cached agents from previous session")
|
1500
|
+
else:
|
1501
|
+
click.echo("ā Failed to switch session")
|
1502
|
+
|
1503
|
+
elif subcmd == 'end':
|
1504
|
+
current = cli_instance.get_current_session()
|
1505
|
+
if current:
|
1506
|
+
# Clear cached agents for this session
|
1507
|
+
cli_instance.clear_session_agents(current)
|
1508
|
+
|
1509
|
+
if cli_instance.clear_current_session():
|
1510
|
+
click.echo(f"ā
Ended session: {current}")
|
1511
|
+
click.echo(f"š§¹ Cleared cached agents for session")
|
1512
|
+
|
1513
|
+
# Auto-create replacement session
|
1514
|
+
cli_instance._ensure_auto_session()
|
1515
|
+
new_session = cli_instance.get_current_session()
|
1516
|
+
if new_session:
|
1517
|
+
click.echo(f"š Auto-created replacement session: {new_session}")
|
1518
|
+
else:
|
1519
|
+
click.echo("ā Failed to end session")
|
1520
|
+
else:
|
1521
|
+
click.echo("ā No active session to end")
|
1522
|
+
|
1523
|
+
elif subcmd == 'history':
|
1524
|
+
limit = 20 # default
|
1525
|
+
if len(args) > 1 and args[1].isdigit():
|
1526
|
+
limit = int(args[1])
|
1527
|
+
return handle_session_history(limit)
|
1528
|
+
|
1529
|
+
elif subcmd == 'summary':
|
1530
|
+
return handle_session_summary()
|
1531
|
+
|
1532
|
+
elif subcmd == 'clear':
|
1533
|
+
# Add confirmation in shell mode
|
1534
|
+
if click.confirm('Are you sure you want to clear the conversation history?'):
|
1535
|
+
return handle_session_clear()
|
1536
|
+
else:
|
1537
|
+
click.echo("ā Operation cancelled")
|
1538
|
+
|
1539
|
+
elif subcmd == 'agents':
|
1540
|
+
# Show cached agents for current session
|
1541
|
+
current_session = cli_instance.get_current_session()
|
1542
|
+
agents_info = cli_instance.get_cached_agents_info()
|
1543
|
+
|
1544
|
+
click.echo(f"š¤ **Cached Agents** (Session: {current_session[:8] if current_session else 'None'}...)")
|
1545
|
+
click.echo(f"š Total: {agents_info['total_agents']}")
|
1546
|
+
click.echo("=" * 40)
|
1547
|
+
|
1548
|
+
if agents_info['agents']:
|
1549
|
+
for agent_info in agents_info['agents']:
|
1550
|
+
click.echo(f"\nš {agent_info['agent_type']}")
|
1551
|
+
click.echo(f" ID: {agent_info['agent_id']}")
|
1552
|
+
click.echo(f" Memory: {'ā
' if agent_info['memory_available'] else 'ā'}")
|
1553
|
+
click.echo(f" Created: {agent_info['created_at']}")
|
1554
|
+
else:
|
1555
|
+
click.echo("\nš No cached agents")
|
1556
|
+
|
1557
|
+
else:
|
1558
|
+
click.echo(f"ā Unknown session command: {subcmd}")
|
1559
|
+
click.echo("š” Available: create, current, status, use, end, history, summary, clear, agents")
|
1560
|
+
|
1561
|
+
return True
|
1562
|
+
|
1563
|
+
def handle_chat_command(args):
|
1564
|
+
"""Handle chat command"""
|
1565
|
+
if not args:
|
1566
|
+
click.echo("ā Usage: chat <message>")
|
1567
|
+
return True
|
1568
|
+
|
1569
|
+
message = ' '.join(args)
|
1570
|
+
|
1571
|
+
# Process chat message
|
1572
|
+
async def process_chat():
|
1573
|
+
# Determine conversation ID
|
1574
|
+
active_session = cli_instance.get_current_session()
|
1575
|
+
if active_session:
|
1576
|
+
conv_id = active_session
|
1577
|
+
session_display = active_session[:8] + "..." if len(active_session) > 8 else active_session
|
1578
|
+
session_source = f"session: {session_display}"
|
1579
|
+
else:
|
1580
|
+
conv_id = "shell"
|
1581
|
+
session_source = "default: shell"
|
1582
|
+
|
1583
|
+
verbose = cli_instance.config.get('cli.verbose', False)
|
1584
|
+
if verbose:
|
1585
|
+
click.echo(f"š¬ Processing: {message}")
|
1586
|
+
click.echo(f"š {session_source}")
|
1587
|
+
|
1588
|
+
try:
|
1589
|
+
response = await cli_instance.smart_message_routing(message)
|
1590
|
+
click.echo(f"\nš¤ Response:\n{response}")
|
1591
|
+
except Exception as e:
|
1592
|
+
click.echo(f"ā Error: {e}")
|
1593
|
+
|
1594
|
+
asyncio.run(process_chat())
|
1595
|
+
return True
|
1596
|
+
|
1597
|
+
def handle_youtube_command(args):
|
1598
|
+
"""Handle YouTube commands"""
|
1599
|
+
if not args:
|
1600
|
+
click.echo("ā Usage: youtube <download|info> <url>")
|
1601
|
+
return True
|
1602
|
+
|
1603
|
+
subcmd = args[0].lower()
|
1604
|
+
if len(args) < 2:
|
1605
|
+
click.echo(f"ā Usage: youtube {subcmd} <url>")
|
1606
|
+
return True
|
1607
|
+
|
1608
|
+
url = args[1]
|
1609
|
+
|
1610
|
+
if subcmd == 'download':
|
1611
|
+
click.echo(f"šŗ Downloading: {url}")
|
1612
|
+
# Route to chat system
|
1613
|
+
return handle_chat_command([f"download video from {url}"])
|
1614
|
+
elif subcmd == 'info':
|
1615
|
+
click.echo(f"š Getting info: {url}")
|
1616
|
+
return handle_chat_command([f"get info about {url}"])
|
1617
|
+
else:
|
1618
|
+
click.echo(f"ā Unknown YouTube command: {subcmd}")
|
1619
|
+
|
1620
|
+
return True
|
1621
|
+
|
1622
|
+
def handle_interactive_command():
|
1623
|
+
"""Handle interactive mode"""
|
1624
|
+
click.echo("š Switching to interactive chat mode...")
|
1625
|
+
click.echo("š” Type 'quit' to return to shell")
|
1626
|
+
|
1627
|
+
# Start interactive chat mode
|
1628
|
+
async def interactive_chat():
|
1629
|
+
while True:
|
1630
|
+
try:
|
1631
|
+
current_session = cli_instance.get_current_session()
|
1632
|
+
if current_session:
|
1633
|
+
session_short = current_session[:8] if len(current_session) > 8 else current_session
|
1634
|
+
prompt_text = f"š£ļø You ({session_short})"
|
1635
|
+
else:
|
1636
|
+
prompt_text = "š£ļø You"
|
1637
|
+
|
1638
|
+
user_input = click.prompt(f"\n{prompt_text}", type=str)
|
1639
|
+
|
1640
|
+
if user_input.lower() in ['quit', 'exit', 'bye']:
|
1641
|
+
click.echo("š Returning to shell...")
|
1642
|
+
break
|
1643
|
+
|
1644
|
+
# Process as chat
|
1645
|
+
response = await cli_instance.smart_message_routing(user_input)
|
1646
|
+
click.echo(f"š¤ Agent: {response}")
|
1647
|
+
|
1648
|
+
except KeyboardInterrupt:
|
1649
|
+
click.echo("\nš Returning to shell...")
|
1650
|
+
break
|
1651
|
+
except EOFError:
|
1652
|
+
click.echo("\nš Returning to shell...")
|
1653
|
+
break
|
1654
|
+
|
1655
|
+
asyncio.run(interactive_chat())
|
1656
|
+
return True
|
1657
|
+
|
1658
|
+
def handle_health_command():
|
1659
|
+
"""Handle health check"""
|
1660
|
+
click.echo("š„ Running health check...")
|
1661
|
+
click.echo("ā
CLI is working")
|
1662
|
+
click.echo("ā
Session management available")
|
1663
|
+
click.echo("ā
Smart routing available")
|
1664
|
+
click.echo("ā
Agent caching enabled")
|
1665
|
+
click.echo(f"ā
Configuration loaded: {cli_instance.config.config_path or 'defaults'}")
|
1666
|
+
|
1667
|
+
# Show cached agents
|
1668
|
+
agents_info = cli_instance.get_cached_agents_info()
|
1669
|
+
click.echo(f"š¤ Cached agents: {agents_info['total_agents']}")
|
1670
|
+
|
1671
|
+
return True
|
1672
|
+
|
1673
|
+
def handle_demo_command():
|
1674
|
+
"""Handle demo"""
|
1675
|
+
click.echo("šŖ Running demo...")
|
1676
|
+
click.echo("š” This would show a demo of the system")
|
1677
|
+
return True
|
1678
|
+
|
1679
|
+
def handle_examples_command():
|
1680
|
+
"""Handle examples"""
|
1681
|
+
click.echo("š Usage Examples:")
|
1682
|
+
click.echo(" config set cli.theme minimal")
|
1683
|
+
click.echo(" session create my_project")
|
1684
|
+
click.echo(" chat 'Hello, I need help with video processing'")
|
1685
|
+
click.echo(" youtube download https://youtube.com/watch?v=example")
|
1686
|
+
click.echo(" session history")
|
1687
|
+
click.echo(" session agents")
|
1688
|
+
click.echo(" config show")
|
1689
|
+
click.echo(" session end")
|
1690
|
+
return True
|
1691
|
+
|
1692
|
+
def handle_agents_command():
|
1693
|
+
"""š Handle agents command - show cached agents"""
|
1694
|
+
agents_info = cli_instance.get_cached_agents_info()
|
1695
|
+
current_session = cli_instance.get_current_session()
|
1696
|
+
|
1697
|
+
click.echo(f"š¤ **Cached Agents** (Session: {current_session[:8] if current_session else 'None'}...)")
|
1698
|
+
click.echo(f"š Total: {agents_info['total_agents']}")
|
1699
|
+
click.echo("=" * 40)
|
1700
|
+
|
1701
|
+
if agents_info['agents']:
|
1702
|
+
for agent_info in agents_info['agents']:
|
1703
|
+
click.echo(f"\nš {agent_info['agent_type']}")
|
1704
|
+
click.echo(f" ID: {agent_info['agent_id']}")
|
1705
|
+
click.echo(f" Memory: {'ā
' if agent_info['memory_available'] else 'ā'}")
|
1706
|
+
click.echo(f" Created: {agent_info['created_at']}")
|
1707
|
+
else:
|
1708
|
+
click.echo("\nš No cached agents")
|
1709
|
+
click.echo("š” Agents will be created when you send messages")
|
1710
|
+
|
1711
|
+
return True
|
1712
|
+
|
1713
|
+
def handle_debug_agents_command():
|
1714
|
+
"""š Handle debug agents command"""
|
1715
|
+
click.echo(f"\nš CACHED AGENTS DEBUG")
|
1716
|
+
click.echo("=" * 50)
|
1717
|
+
|
1718
|
+
agents_info = cli_instance.get_cached_agents_info()
|
1719
|
+
current_session = cli_instance.get_current_session()
|
1720
|
+
|
1721
|
+
click.echo(f"Current session: {current_session}")
|
1722
|
+
click.echo(f"Total cached agents: {agents_info['total_agents']}")
|
1723
|
+
|
1724
|
+
if agents_info['agents']:
|
1725
|
+
for agent_info in agents_info['agents']:
|
1726
|
+
click.echo(f"\nš¤ {agent_info['agent_type']}:")
|
1727
|
+
click.echo(f" Cache Key: {agent_info['cache_key']}")
|
1728
|
+
click.echo(f" Agent ID: {agent_info['agent_id']}")
|
1729
|
+
click.echo(f" Session: {agent_info['session_id']}")
|
1730
|
+
click.echo(f" Memory: {'ā
' if agent_info['memory_available'] else 'ā'}")
|
1731
|
+
click.echo(f" Created: {agent_info['created_at']}")
|
1732
|
+
else:
|
1733
|
+
click.echo("\nš No cached agents")
|
1734
|
+
|
1735
|
+
return True
|
1736
|
+
|
1737
|
+
# Main shell loop
|
1738
|
+
try:
|
1739
|
+
while True:
|
1740
|
+
try:
|
1741
|
+
prompt = get_prompt()
|
1742
|
+
|
1743
|
+
# Use input() instead of click.prompt() for better control
|
1744
|
+
try:
|
1745
|
+
command_line = input(prompt)
|
1746
|
+
except (KeyboardInterrupt, EOFError):
|
1747
|
+
click.echo("\nš Goodbye!")
|
1748
|
+
break
|
1749
|
+
except Exception as e:
|
1750
|
+
click.echo(f"\nā ļø Input error: {e}")
|
1751
|
+
continue
|
1752
|
+
|
1753
|
+
# Process command
|
1754
|
+
if not process_shell_command(command_line):
|
1755
|
+
break
|
1756
|
+
|
1757
|
+
except KeyboardInterrupt:
|
1758
|
+
click.echo("\nš” Use 'exit' to quit")
|
1759
|
+
continue
|
1760
|
+
except EOFError:
|
1761
|
+
click.echo("\nš Goodbye!")
|
1762
|
+
break
|
1763
|
+
|
1764
|
+
except Exception as e:
|
1765
|
+
click.echo(f"ā Shell error: {e}")
|
1766
|
+
|
1767
|
+
|
1768
|
+
@cli.command()
|
1769
|
+
@click.argument('message')
|
1770
|
+
@click.option('--conversation', '-conv', help='Conversation ID (overrides active session)')
|
1771
|
+
@click.option('--format', '-f', type=click.Choice(['text', 'json']), default='text', help='Output format')
|
1772
|
+
def chat(message: str, conversation: Optional[str], format: str):
|
1773
|
+
"""Send a message using smart agent routing with configuration support"""
|
1774
|
+
|
1775
|
+
# Determine conversation ID
|
1776
|
+
if conversation:
|
1777
|
+
conv_id = conversation
|
1778
|
+
session_source = f"explicit: {conversation}"
|
1779
|
+
else:
|
1780
|
+
active_session = cli_instance.get_current_session()
|
1781
|
+
if active_session:
|
1782
|
+
conv_id = active_session
|
1783
|
+
session_source = f"active session: {active_session}"
|
1784
|
+
else:
|
1785
|
+
conv_id = "cli"
|
1786
|
+
session_source = "default: cli"
|
1787
|
+
|
1788
|
+
verbose = cli_instance.config.get('cli.verbose', False)
|
1789
|
+
if verbose:
|
1790
|
+
click.echo(f"š¬ Processing: {message}")
|
1791
|
+
click.echo(f"š Session: {session_source}")
|
1792
|
+
|
1793
|
+
async def process():
|
1794
|
+
start_time = time.time()
|
1795
|
+
response = await cli_instance.smart_message_routing(message)
|
1796
|
+
processing_time = time.time() - start_time
|
1797
|
+
|
1798
|
+
if format == 'json':
|
1799
|
+
result = {
|
1800
|
+
'success': True,
|
1801
|
+
'response': response,
|
1802
|
+
'processing_time': processing_time,
|
1803
|
+
'message': message,
|
1804
|
+
'conversation_id': conv_id,
|
1805
|
+
'session_source': session_source,
|
1806
|
+
'paradigm': 'cached_agent_reuse',
|
1807
|
+
'config_loaded': cli_instance.config.config_path is not None
|
1808
|
+
}
|
1809
|
+
click.echo(json.dumps(result, indent=2))
|
1810
|
+
else:
|
1811
|
+
click.echo(f"\nš¤ Response:\n{response}")
|
1812
|
+
if verbose:
|
1813
|
+
click.echo(f"\nā±ļø Processing time: {processing_time:.2f}s")
|
1814
|
+
click.echo(f"š Conversation: {conv_id}")
|
1815
|
+
click.echo(f"š Using cached agent reuse")
|
1816
|
+
|
1817
|
+
asyncio.run(process())
|
1818
|
+
|
1819
|
+
|
1820
|
+
@cli.command()
|
1821
|
+
def interactive():
|
1822
|
+
"""Interactive chat mode"""
|
1823
|
+
|
1824
|
+
click.echo("š¤ Starting interactive chat mode...")
|
1825
|
+
|
1826
|
+
# Check for active session
|
1827
|
+
active_session = cli_instance.get_current_session()
|
1828
|
+
|
1829
|
+
if active_session:
|
1830
|
+
session_display = active_session[:8] + "..." if len(active_session) > 8 else active_session
|
1831
|
+
click.echo(f"š Using active session: {session_display}")
|
1832
|
+
else:
|
1833
|
+
click.echo("š No active session - using default conversation")
|
1834
|
+
|
1835
|
+
click.echo("Type 'quit', 'exit', or 'bye' to exit")
|
1836
|
+
click.echo("-" * 60)
|
1837
|
+
|
1838
|
+
async def interactive_loop():
|
1839
|
+
# Use active session or generate a unique one for this interactive session
|
1840
|
+
if active_session:
|
1841
|
+
conversation_id = active_session
|
1842
|
+
else:
|
1843
|
+
conversation_id = f"interactive_{int(time.time())}"
|
1844
|
+
|
1845
|
+
while True:
|
1846
|
+
try:
|
1847
|
+
user_input = click.prompt("\nš£ļø You", type=str)
|
1848
|
+
|
1849
|
+
if user_input.lower() in ['quit', 'exit', 'bye']:
|
1850
|
+
click.echo("š Goodbye!")
|
1851
|
+
break
|
1852
|
+
|
1853
|
+
# Process with smart routing using conversation_id
|
1854
|
+
response = await cli_instance.smart_message_routing(user_input)
|
1855
|
+
|
1856
|
+
click.echo(f"š¤ Agent: {response}")
|
1857
|
+
session_display = conversation_id[:8] + "..." if len(conversation_id) > 8 else conversation_id
|
1858
|
+
click.echo(f"š Session: {session_display}")
|
1859
|
+
|
1860
|
+
except KeyboardInterrupt:
|
1861
|
+
click.echo("\nš Goodbye!")
|
1862
|
+
break
|
1863
|
+
except EOFError:
|
1864
|
+
click.echo("\nš Goodbye!")
|
1865
|
+
break
|
1866
|
+
|
1867
|
+
asyncio.run(interactive_loop())
|
1868
|
+
|
1869
|
+
|
1870
|
+
if __name__ == '__main__':
|
1871
|
+
cli()
|