Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions runagent/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,28 +727,59 @@ def run(agent_id, input_file, message, local, direct, timeout):
@click.option("--cleanup-days", type=int, help="Clean up records older than N days")
@click.option("--agent-id", help="Show detailed info for specific agent")
@click.option("--capacity", is_flag=True, help="Show detailed capacity information")
def db_status(cleanup_days, agent_id, capacity):
@click.option("--servers", is_flag=True, help="Show running server status")
def db_status(cleanup_days, agent_id, capacity, servers):
"""Show local database status and statistics"""

try:
sdk = RunAgent()

if servers:
# Show server status
capacity_info = sdk.get_local_capacity()

console.print(f"\n🖥️ [bold]Server Status[/bold]")

running_servers = capacity_info.get("running_servers", [])
stopped_servers = capacity_info.get("stopped_servers", [])

if running_servers:
console.print(f"\n🟢 [bold]Running Servers ({len(running_servers)}):[/bold]")
for server in running_servers:
console.print(f" • [green]{server['agent_id']}[/green] - {server['framework']} on {server['host']}:{server['port']}")
console.print(f" Runs: {server['run_count']}, Last: {server['last_run'] or 'Never'}")

if stopped_servers:
console.print(f"\n🔴 [bold]Stopped Servers ({len(stopped_servers)}):[/bold]")
for server in stopped_servers:
console.print(f" • [red]{server['agent_id']}[/red] - {server['framework']} on {server['host']}:{server['port']}")
console.print(f" Runs: {server['run_count']}, Last: {server['last_run'] or 'Never'}")

summary = capacity_info.get("server_summary", {})
console.print(f"\n📊 [bold]Summary:[/bold]")
console.print(f" Total Agents: [cyan]{summary.get('total_agents', 0)}[/cyan]")
console.print(f" Running Servers: [green]{summary.get('running_servers', 0)}[/green]")
console.print(f" Stopped Servers: [red]{summary.get('stopped_servers', 0)}[/red]")
console.print(f" Unique Ports: [yellow]{summary.get('unique_ports', 0)}[/yellow]")

return

if capacity:
# Show detailed capacity info
capacity_info = sdk.get_local_capacity()

console.print(f"\n📊 [bold]Database Capacity Information[/bold]")
console.print(
f"Current: [cyan]{capacity_info.get('current_count', 0)}/5[/cyan] agents"
f"Current: [cyan]{capacity_info.get('current_count', 0)}/{capacity_info.get('max_capacity', 5)}[/cyan] agents"
)
console.print(
f"Remaining slots: [green]{capacity_info.get('remaining_slots', 0)}[/green]"
)

status = "🔴 FULL" if capacity_info.get("is_full") else "🟢 Available"
status = "🔴 FULL" if capacity_info.get('is_full') else "🟢 Available"
console.print(f"Status: {status}")

agents = capacity_info.get("agents", [])
agents = capacity_info.get('agents', [])
if agents:
console.print(f"\n📋 [bold]Deployed Agents (by age):[/bold]")
for i, agent in enumerate(agents):
Expand All @@ -763,11 +794,11 @@ def db_status(cleanup_days, agent_id, capacity):
else " (newest)" if i == len(agents) - 1 else ""
)
console.print(
f" {i+1}. {status_icon} [magenta]{agent['agent_id']}[/magenta] ({agent['framework']}) - {agent['deployed_at']}{age_label}"
f" {i+1}. {status_icon} [magenta]{agent['agent_id']}[/magenta] ({agent['framework']}) - {agent['host']}:{agent['port']}{age_label}"
)

if capacity_info.get("is_full"):
oldest = capacity_info.get("oldest_agent", {})
if capacity_info.get('is_full'):
oldest = capacity_info.get('oldest_agent', {})
console.print(
f"\n💡 [yellow]To deploy new agent, replace oldest:[/yellow]"
)
Expand All @@ -776,6 +807,9 @@ def db_status(cleanup_days, agent_id, capacity):
)

return
except Exception as e:
console.print(f"❌ [red]Database status error:[/red] {e}")
raise click.ClickException("Failed to get database status")

if agent_id:
# Show specific agent info
Expand Down
44 changes: 39 additions & 5 deletions runagent/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
from runagent.sdk.rest_client import RestClient
from runagent.utils.agent import detect_framework, validate_agent
from runagent.sdk.socket_client import SocketClient
import requests


class RunAgentClient:
def __init__(self, agent_id: str, local: bool = True):
def __init__(self, agent_id: str, local: bool = True, port: int = None, host: str = None):
self.sdk = RunAgentSDK()
self.agent_id = agent_id
self.local = local
Expand All @@ -16,21 +17,54 @@ def __init__(self, agent_id: str, local: bool = True):
raise ValueError(f"Agent {agent_id} not found in local DB")
self.agent_info = agent_info

agent_host = self.agent_info["host"]
agent_port = self.agent_info["port"]
# Use provided port/host or fall back to database values
agent_host = host or self.agent_info["host"]
agent_port = port or self.agent_info["port"]

# Try to connect to the specified port first
if port is not None:
test_url = f"http://{agent_host}:{port}/api/v1/health"
try:
response = requests.get(test_url, timeout=2)
if response.status_code == 200:
agent_port = port
print(f"✅ Connected to server on port {port}")
else:
print(f"⚠️ Server on port {port} returned status {response.status_code}, using database port {agent_port}")
except requests.exceptions.RequestException:
print(f"⚠️ Could not connect to port {port}, using database port {agent_port}")

agent_base_url = f"http://{agent_host}:{agent_port}"
agent_socket_url = f"ws://{agent_host}:{agent_port}"

self.rest_client = RestClient(base_url=agent_base_url, api_prefix="/api/v1")
self.socket_client = SocketClient(
base_socket_url=agent_socket_url,
api_prefix="/api/v1"
)
)
else:
self.rest_client = RestClient()
self.socket_client = SocketClient()

def run_generic(self, *input_args, **input_kwargs):
"""
Run agent with generic interface - simplified for easy testing

Args:
*input_args: Can be a simple string message or complex arguments
**input_kwargs: Keyword arguments for the agent

Returns:
Agent response
"""
# Handle simple string input
if len(input_args) == 1 and isinstance(input_args[0], str) and not input_kwargs:
# Convert simple string to messages format
input_kwargs = {
"messages": [{"role": "user", "content": input_args[0]}]
}
input_args = ()

return self.rest_client.run_agent_generic(
self.agent_id, input_args=input_args, input_kwargs=input_kwargs
)
Expand All @@ -52,4 +86,4 @@ async def run_generic_stream(self, *input_args, **input_kwargs):
async for item in self.socket_client.run_agent_generic_stream_async(
self.agent_id, *input_args, **input_kwargs
):
yield item
yield item
112 changes: 112 additions & 0 deletions runagent/sdk/sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(
self.templates = TemplateManager()
self.db_service = DBService()
# self.local = LocalDeployment(self.config)
self.local = LocalDeploymentManager(self.db_service)
self.remote = RemoteDeployment(self.config)

# Validate configuration on initialization
Expand Down Expand Up @@ -507,3 +508,114 @@ def __exit__(self, exc_type, exc_val, exc_tb):
# String representation
def __repr__(self):
return f"RunAgentSDK(configured={self.is_configured()})"




class LocalDeploymentManager:
"""Manager for local agent deployments"""

def __init__(self, db_service):
self.db_service = db_service

def list_agents(self) -> t.List[t.Dict[str, t.Any]]:
"""List all locally deployed agents"""
agents = self.db_service.list_agents()

# Add additional info like file existence
for agent in agents:
agent_path = Path(agent["agent_path"])
agent["exists"] = agent_path.exists()
agent["deployment_exists"] = agent_path.exists()
agent["source_exists"] = agent_path.exists()

return agents

def get_capacity_info(self) -> t.Dict[str, t.Any]:
"""Get local database capacity information"""
return self.db_service.get_database_capacity_info()

def get_agent_info(self, agent_id: str) -> t.Dict[str, t.Any]:
"""Get comprehensive information about a local agent"""
agent_data = self.db_service.get_agent(agent_id)
if not agent_data:
return {"success": False, "error": f"Agent {agent_id} not found"}

# Add file existence checks
agent_path = Path(agent_data["agent_path"])

# Get agent statistics
stats = self.db_service.get_agent_stats(agent_id)

return {
"success": True,
"agent_info": {
**agent_data,
"deployment_exists": agent_path.exists(),
"source_exists": agent_path.exists(),
"deployment_path": str(agent_path),
"folder_path": str(agent_path),
"stats": stats,
}
}

def delete_agent(self, agent_id: str) -> t.Dict[str, t.Any]:
"""Delete agent (disabled for safety)"""
return {
"success": False,
"error": "Agent deletion is disabled for safety. Use database cleanup instead.",
}

def cleanup_old_records(self, days_old: int = 30) -> t.Dict[str, t.Any]:
"""Clean up old database records"""
try:
deleted_count = self.db_service.cleanup_old_runs(days_old)
return {
"success": True,
"message": f"Cleaned up {deleted_count} old run records",
"deleted_count": deleted_count,
}
except Exception as e:
return {"success": False, "error": str(e)}

def get_database_stats(self) -> t.Dict[str, t.Any]:
"""Get database statistics"""
return self.db_service.get_database_stats()

def run_agent(self, agent_id: str, input_data: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
"""Run agent locally via HTTP"""
try:
agent_info = self.db_service.get_agent(agent_id)
if not agent_info:
return {"success": False, "error": f"Agent {agent_id} not found"}

# Try to connect to the agent's server
import requests
import time

start_time = time.time()
url = f"http://{agent_info['host']}:{agent_info['port']}/api/v1/agents/{agent_id}/execute/generic"

try:
response = requests.post(url, json={"input_data": input_data}, timeout=30)
execution_time = time.time() - start_time

if response.status_code == 200:
result = response.json()
result["execution_time"] = execution_time
return result
else:
return {
"success": False,
"error": f"Server returned status {response.status_code}: {response.text}",
"execution_time": execution_time,
}
except requests.exceptions.RequestException as e:
execution_time = time.time() - start_time
return {
"success": False,
"error": f"Server not reachable: {str(e)}",
"execution_time": execution_time,
}
except Exception as e:
return {"success": False, "error": str(e)}
11 changes: 9 additions & 2 deletions runagent/sdk/server/framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from typing import Dict

from runagent.sdk.server.framework.langgraph import LangGraphExecutor
from runagent.sdk.server.framework.langchain import LangChainExecutor
from runagent.sdk.server.framework.generic import GenericExecutor
from runagent.utils.schema import EntryPoint, EntryPointType


Expand All @@ -15,9 +17,14 @@ def get_executor(
valid_entrypoint_found = True
break
if not valid_entrypoint_found:
raise ValueError(f"No valid entrypoint type found in agent configuration. Valid types are: {[t.value for t in EntrypointType]}")
raise ValueError(f"No valid entrypoint type found in agent configuration. Valid types are: {[t.value for t in EntryPointType]}")

if framework == "langgraph":
return LangGraphExecutor(agent_dir, agent_entrypoints)
elif framework == "langchain":
return LangChainExecutor(agent_dir, agent_entrypoints)
elif framework in ["llamaindex", "crewai", "autogen", "letta"]:
# Use generic executor for other frameworks
return GenericExecutor(agent_dir, agent_entrypoints)
else:
raise ValueError(f"Framework {framework} not supported yet.")
raise ValueError(f"Framework {framework} not supported yet.")
Loading