Skip to content
Merged
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
471 changes: 299 additions & 172 deletions runagent/cli/commands.py

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions runagent/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ def runagent():
runagent.add_command(commands.run)
runagent.add_command(commands.delete)
runagent.add_command(commands.db)
runagent.add_command(commands.local_sync)
if __name__ == "__main__":
runagent()
43 changes: 32 additions & 11 deletions runagent/sdk/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def setup(
api_key: t.Optional[str] = None,
base_url: t.Optional[str] = None,
save: bool = True,
validate_auth: bool = True, # NEW: Add option to skip validation
) -> bool:
"""
Setup and validate configuration.
Expand All @@ -99,6 +100,7 @@ def setup(
api_key: API key for authentication
base_url: Base URL for the service
save: Whether to save to config file
validate_auth: Whether to validate authentication (can be disabled for testing)

Returns:
True if setup is successful
Expand All @@ -119,9 +121,10 @@ def setup(
if not self._config.get("api_key"):
raise ValidationError("API key is required")

# Test authentication
if not self._test_authentication():
raise AuthenticationError("Authentication failed with provided credentials")
# Test authentication if requested
if validate_auth:
if not self._test_authentication():
raise AuthenticationError("Authentication failed with provided credentials")

# Save if requested
if save:
Expand All @@ -132,15 +135,33 @@ def setup(
def _test_authentication(self) -> bool:
"""Test authentication with current configuration"""
try:
from .http import EndpointHandler
from .rest_client import RestClient

handler = EndpointHandler(
client = RestClient(
api_key=self._config.get("api_key"),
base_url=self._config.get("base_url"),
)
response = handler.validate_api_key()
return response.get("status") == "success"
except Exception:

# Use the new auth validation endpoint
response = client.http.get("/auth/validate", timeout=10)

if response.status_code == 200:
user_data = response.json()

# Store user info for later display
if user_data.get("status") == "success" and user_data.get("user"):
self._config.update({
"user_email": user_data["user"].get("email"),
"user_id": user_data["user"].get("id"),
"user_tier": user_data["user"].get("tier", "free")
})

return True
else:
return False

except Exception as e:
print(f"Authentication test failed: {e}")
return False

def is_configured(self) -> bool:
Expand All @@ -159,9 +180,9 @@ def get_status(self) -> t.Dict[str, t.Any]:
"api_key_set": bool(self._config.get("api_key")),
"base_url": self._config.get("base_url"),
"user_info": {
k: v
for k, v in self._config.items()
if k not in ["api_key", "base_url"]
"email": self._config.get("user_email"),
"user_id": self._config.get("user_id"),
"tier": self._config.get("user_tier")
},
"config_file": str(self.config_file),
"config_file_exists": self.config_file.exists(),
Expand Down
110 changes: 110 additions & 0 deletions runagent/sdk/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class Agent(Base):
invocations = relationship(
"AgentInvocation", back_populates="agent", cascade="all, delete-orphan"
)
agent_logs = relationship(
"AgentLog", back_populates="agent", cascade="all, delete-orphan"
)

# Indexes
__table_args__ = (Index("idx_agents_status", "status"),)
Expand Down Expand Up @@ -134,6 +137,31 @@ class AgentInvocation(Base):
Index("idx_invocations_agent_status", "agent_id", "status"), # Composite index
)


class AgentLog(Base):
"""Agent log model for storing detailed logs"""

__tablename__ = "agent_logs"

id = Column(Integer, primary_key=True, autoincrement=True)
agent_id = Column(
String, ForeignKey("agents.agent_id", ondelete="CASCADE"), nullable=False
)
log_level = Column(String, nullable=False)
message = Column(Text, nullable=False)
created_at = Column(DateTime, default=func.current_timestamp())
execution_id = Column(String, nullable=True)

# Relationship
agent = relationship("Agent", back_populates="agent_logs")

# Indexes
__table_args__ = (
Index("idx_agent_logs_agent_id", "agent_id"),
Index("idx_agent_logs_created_at", "created_at"),
Index("idx_agent_logs_level", "log_level"),
)

class DBManager:
"""Low-level database manager for SQLAlchemy operations"""

Expand Down Expand Up @@ -1763,4 +1791,86 @@ def force_delete_agent(self, agent_id: str) -> Dict:
}


def record_agent_log(
self,
agent_id: str,
log_level: str,
message: str,
execution_id: str = None
) -> int:
"""Record an agent log entry"""
with self.db_manager.get_session() as session:
try:
# Create new log record
log_entry = AgentLog(
agent_id=agent_id,
log_level=log_level.upper(),
message=message,
execution_id=execution_id,
created_at=func.current_timestamp()
)

session.add(log_entry)
session.flush() # Get the ID
log_id = log_entry.id
session.commit()
return log_id
except Exception as e:
session.rollback()
console.print(f"Error recording agent log: {e}")
return -1

def get_agent_logs(
self,
agent_id: str,
limit: int = 100,
log_level: str = None,
execution_id: str = None
) -> List[Dict]:
"""Get agent logs with optional filtering"""
with self.db_manager.get_session() as session:
try:
query = session.query(AgentLog).filter(
AgentLog.agent_id == agent_id
)

if log_level:
query = query.filter(AgentLog.log_level == log_level.upper())

if execution_id:
query = query.filter(AgentLog.execution_id == execution_id)

logs = query.order_by(desc(AgentLog.created_at)).limit(limit).all()

return [
{
"id": log.id,
"agent_id": log.agent_id,
"log_level": log.log_level,
"message": log.message,
"execution_id": log.execution_id,
"created_at": log.created_at.isoformat() if log.created_at else None,
}
for log in logs
]
except Exception as e:
console.print(f"Error getting agent logs: {e}")
return []

def cleanup_old_logs(self, days_old: int = 7) -> int:
"""Clean up old log records (logs are more ephemeral than runs)"""
with self.db_manager.get_session() as session:
try:
cutoff_date = datetime.now() - timedelta(days=days_old)
deleted_count = (
session.query(AgentLog)
.filter(AgentLog.created_at < cutoff_date)
.delete()
)
session.commit()
console.print(f"🧹 [green]Cleaned up {deleted_count} old log entries[/green]")
return deleted_count
except Exception as e:
session.rollback()
console.print(f"Error cleaning up old logs: {e}")
return 0
11 changes: 8 additions & 3 deletions runagent/sdk/deployment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
Deployment modules for local and remote agent deployment.
"""

# from .local import LocalDeployment
from .remote import RemoteDeployment
from runagent.sdk.deployment.remote import RemoteDeployment

__all__ = ["LocalDeployment", "RemoteDeployment"]
# Import middleware sync components
try:
from runagent.sdk.deployment.middleware_sync import get_middleware_sync, MiddlewareSync
__all__ = ["RemoteDeployment", "get_middleware_sync", "MiddlewareSync"]
except ImportError:
# Fallback if middleware_sync is not available
__all__ = ["RemoteDeployment"]
Loading