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
11 changes: 11 additions & 0 deletions src/praisonai-agents/praisonaiagents/managed/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,17 @@
ComputeConfig,
InstanceInfo,
InstanceStatus,
SessionInfo,
)

# Lazy re-export of ManagedBackendProtocol from agent.protocols
# Following AGENTS.md protocol-driven design principles
def __getattr__(name: str):
if name == "ManagedBackendProtocol":
from ..agent.protocols import ManagedBackendProtocol
return ManagedBackendProtocol
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")

__all__ = [
"ManagedEvent",
"AgentMessageEvent",
Expand All @@ -41,4 +50,6 @@
"ComputeConfig",
"InstanceInfo",
"InstanceStatus",
"SessionInfo",
"ManagedBackendProtocol", # Lazy re-export from agent.protocols
]
30 changes: 30 additions & 0 deletions src/praisonai-agents/praisonaiagents/managed/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,36 @@ class InstanceInfo:
metadata: Dict[str, Any] = field(default_factory=dict)


@dataclass
class SessionInfo:
"""Unified session information schema for all managed backends.

Provides a consistent interface for session metadata across
Anthropic Managed Agents and Local Managed Agents.
"""
id: str
status: Optional[str] = None
usage: Optional[Dict[str, int]] = None

@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "SessionInfo":
"""Create SessionInfo from backend-specific dict."""
return cls(
id=data["id"],
status=data.get("status"),
usage=data.get("usage")
)

def to_dict(self) -> Dict[str, Any]:
"""Convert to dict format expected by ManagedBackendProtocol."""
result = {"id": self.id}
if self.status is not None:
result["status"] = self.status
if self.usage is not None:
result["usage"] = self.usage
return result


@runtime_checkable
class ComputeProviderProtocol(Protocol):
"""Protocol for compute backends that host managed agent sandboxes.
Expand Down
122 changes: 120 additions & 2 deletions src/praisonai/praisonai/cli/commands/managed.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,32 @@ def managed_multi(
typer.echo(f"Output tokens: {managed.total_output_tokens}")


@sessions_app.command("delete")
def sessions_delete(
session_id: str = typer.Argument(..., help="Session ID to delete (sesn_01...)"),
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt"),
):
"""Delete a managed session.

Example:
praisonai managed sessions delete sesn_01AbCdEf
praisonai managed sessions delete sesn_01AbCdEf --force
"""
if not force:
confirm = typer.confirm(f"Are you sure you want to delete session {session_id}?")
if not confirm:
typer.echo("Cancelled.")
return

try:
client = _get_client()
client.beta.sessions.delete(session_id)
typer.echo(f"Deleted session: {session_id}")
except Exception as e:
typer.echo(f"Error deleting session: {e}", err=True)
raise typer.Exit(1)


# ─────────────────────────────────────────────────────────────────────────────
# sessions sub-commands
# ─────────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -393,8 +419,38 @@ def agents_update(
if not kwargs:
typer.echo("Nothing to update. Pass --name, --system, or --model.")
raise typer.Exit(0)
updated = client.beta.agents.update(agent_id, **kwargs)
typer.echo(f"Updated agent: {updated.id} (v{getattr(updated,'version','')})")
try:
updated = client.beta.agents.update(agent_id, **kwargs)
typer.echo(f"Updated agent: {updated.id} (v{getattr(updated,'version','')})")
except Exception as e:
typer.echo(f"Error updating agent {agent_id}: {e}", err=True)
raise typer.Exit(1)


@agents_app.command("delete")
def agents_delete(
agent_id: str = typer.Argument(..., help="Agent ID to delete (agent_01...)"),
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt"),
):
"""Delete a managed agent.

Example:
praisonai managed agents delete agent_01AbCdEf
praisonai managed agents delete agent_01AbCdEf --force
"""
if not force:
confirm = typer.confirm(f"Are you sure you want to delete agent {agent_id}?")
if not confirm:
typer.echo("Cancelled.")
return

try:
client = _get_client()
client.beta.agents.delete(agent_id)
typer.echo(f"Deleted agent: {agent_id}")
except Exception as e:
typer.echo(f"Error deleting agent: {e}", err=True)
raise typer.Exit(1)


# ─────────────────────────────────────────────────────────────────────────────
Expand Down Expand Up @@ -441,6 +497,68 @@ def envs_get(
typer.echo(f"Config: {cfg}")


@envs_app.command("update")
def envs_update(
env_id: str = typer.Argument(..., help="Environment ID to update (env_01...)"),
name: Optional[str] = typer.Option(None, "--name", "-n", help="New environment name"),
config: Optional[str] = typer.Option(None, "--config", "-c", help="New environment config (JSON)"),
):
"""Update a managed environment.

Example:
praisonai managed envs update env_01AbCdEf --name "Updated Env"
praisonai managed envs update env_01AbCdEf --config '{"packages": ["numpy"]}'
"""
client = _get_client()
kwargs = {}
if name:
kwargs["name"] = name
if config:
import json
try:
kwargs["config"] = json.loads(config)
except json.JSONDecodeError as e:
typer.echo(f"Error parsing config JSON: {e}", err=True)
raise typer.Exit(1)

if not kwargs:
typer.echo("No update fields provided. Use --name or --config.", err=True)
raise typer.Exit(1)

try:
updated = client.beta.environments.update(env_id, **kwargs)
typer.echo(f"Updated environment: {updated.id}")
except Exception as e:
typer.echo(f"Error updating environment: {e}", err=True)
raise typer.Exit(1)


@envs_app.command("delete")
def envs_delete(
env_id: str = typer.Argument(..., help="Environment ID to delete (env_01...)"),
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation prompt"),
):
"""Delete a managed environment.

Example:
praisonai managed envs delete env_01AbCdEf
praisonai managed envs delete env_01AbCdEf --force
"""
if not force:
confirm = typer.confirm(f"Are you sure you want to delete environment {env_id}?")
if not confirm:
typer.echo("Cancelled.")
return

try:
client = _get_client()
client.beta.environments.delete(env_id)
typer.echo(f"Deleted environment: {env_id}")
except Exception as e:
typer.echo(f"Error deleting environment: {e}", err=True)
raise typer.Exit(1)


# ─────────────────────────────────────────────────────────────────────────────
# ids sub-commands (save / restore / show — no Anthropic IDs are user-defined)
# ─────────────────────────────────────────────────────────────────────────────
Expand Down
21 changes: 14 additions & 7 deletions src/praisonai/praisonai/integrations/managed_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -565,22 +565,29 @@ def interrupt(self) -> None:
# retrieve_session — ManagedBackendProtocol
# ------------------------------------------------------------------
def retrieve_session(self) -> Dict[str, Any]:
"""Retrieve current session metadata and usage from the API."""
"""Retrieve current session metadata and usage from the API using unified schema."""
if not self._session_id:
return {}
client = self._get_client()
sess = client.beta.sessions.retrieve(self._session_id)
result: Dict[str, Any] = {
"id": getattr(sess, "id", self._session_id),
"status": getattr(sess, "status", None),
}

# Build usage dict if available
usage_dict = None
usage = getattr(sess, "usage", None)
if usage:
result["usage"] = {
usage_dict = {
"input_tokens": getattr(usage, "input_tokens", 0),
"output_tokens": getattr(usage, "output_tokens", 0),
}
return result

# Use unified SessionInfo schema for consistency with Local backend
from praisonaiagents.managed import SessionInfo
session_info = SessionInfo(
id=getattr(sess, "id", self._session_id),
status=getattr(sess, "status", None),
usage=usage_dict
)
return session_info.to_dict()

# ------------------------------------------------------------------
# list_sessions — ManagedBackendProtocol
Expand Down
Loading