118 lines
5.2 KiB
Python
118 lines
5.2 KiB
Python
import asyncio
|
|
from typing import Any, Awaitable, Callable, Dict, List, Optional
|
|
|
|
from fastapi import HTTPException
|
|
from sqlmodel import Session
|
|
|
|
from models.bot import BotInstance
|
|
from providers.provision.base import ProvisionProvider
|
|
from providers.runtime.base import RuntimeProvider
|
|
from services.platform_activity_service import record_activity_event
|
|
|
|
|
|
class LocalRuntimeProvider(RuntimeProvider):
|
|
def __init__(
|
|
self,
|
|
*,
|
|
docker_manager: Any,
|
|
on_state_change: Callable[[str, dict], None],
|
|
provision_provider: ProvisionProvider,
|
|
read_runtime_snapshot: Callable[[BotInstance], Dict[str, Any]],
|
|
resolve_env_params: Callable[[str], Dict[str, str]],
|
|
write_env_store: Callable[[str, Dict[str, str]], None],
|
|
invalidate_bot_cache: Callable[[str], None],
|
|
record_agent_loop_ready_warning: Callable[[str], Awaitable[None]],
|
|
safe_float: Callable[[Any, float], float],
|
|
safe_int: Callable[[Any, int], int],
|
|
) -> None:
|
|
self._docker_manager = docker_manager
|
|
self._on_state_change = on_state_change
|
|
self._provision_provider = provision_provider
|
|
self._read_runtime_snapshot = read_runtime_snapshot
|
|
self._resolve_env_params = resolve_env_params
|
|
self._write_env_store = write_env_store
|
|
self._invalidate_bot_cache = invalidate_bot_cache
|
|
self._record_agent_loop_ready_warning = record_agent_loop_ready_warning
|
|
self._safe_float = safe_float
|
|
self._safe_int = safe_int
|
|
|
|
async def start_bot(self, *, session: Session, bot: BotInstance) -> Dict[str, Any]:
|
|
bot_id = str(bot.id or "").strip()
|
|
if not bot_id:
|
|
raise HTTPException(status_code=400, detail="Bot id is required")
|
|
if not bool(getattr(bot, "enabled", True)):
|
|
raise HTTPException(status_code=403, detail="Bot is disabled. Enable it first.")
|
|
|
|
self._provision_provider.sync_bot_workspace(session=session, bot_id=bot_id)
|
|
runtime_snapshot = self._read_runtime_snapshot(bot)
|
|
env_params = self._resolve_env_params(bot_id)
|
|
self._write_env_store(bot_id, env_params)
|
|
success = self._docker_manager.start_bot(
|
|
bot_id,
|
|
image_tag=bot.image_tag,
|
|
on_state_change=self._on_state_change,
|
|
env_vars=env_params,
|
|
cpu_cores=self._safe_float(runtime_snapshot.get("cpu_cores"), 1.0),
|
|
memory_mb=self._safe_int(runtime_snapshot.get("memory_mb"), 1024),
|
|
storage_gb=self._safe_int(runtime_snapshot.get("storage_gb"), 10),
|
|
)
|
|
if not success:
|
|
bot.docker_status = "STOPPED"
|
|
session.add(bot)
|
|
session.commit()
|
|
raise HTTPException(status_code=500, detail=f"Failed to start container with image {bot.image_tag}")
|
|
|
|
actual_status = self._docker_manager.get_bot_status(bot_id)
|
|
bot.docker_status = actual_status
|
|
if actual_status != "RUNNING":
|
|
session.add(bot)
|
|
session.commit()
|
|
self._invalidate_bot_cache(bot_id)
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail="Bot container failed shortly after startup. Check bot logs/config.",
|
|
)
|
|
|
|
asyncio.create_task(self._record_agent_loop_ready_warning(bot_id))
|
|
session.add(bot)
|
|
record_activity_event(session, bot_id, "bot_started", channel="system", detail=f"Container started for {bot_id}")
|
|
session.commit()
|
|
self._invalidate_bot_cache(bot_id)
|
|
return {"status": "started"}
|
|
|
|
def stop_bot(self, *, session: Session, bot: BotInstance) -> Dict[str, Any]:
|
|
bot_id = str(bot.id or "").strip()
|
|
if not bot_id:
|
|
raise HTTPException(status_code=400, detail="Bot id is required")
|
|
if not bool(getattr(bot, "enabled", True)):
|
|
raise HTTPException(status_code=403, detail="Bot is disabled. Enable it first.")
|
|
|
|
self._docker_manager.stop_bot(bot_id)
|
|
bot.docker_status = "STOPPED"
|
|
session.add(bot)
|
|
record_activity_event(session, bot_id, "bot_stopped", channel="system", detail=f"Container stopped for {bot_id}")
|
|
session.commit()
|
|
self._invalidate_bot_cache(bot_id)
|
|
return {"status": "stopped"}
|
|
|
|
def deliver_command(self, *, bot_id: str, command: str, media: Optional[List[str]] = None) -> Optional[str]:
|
|
success = self._docker_manager.send_command(bot_id, command, media=media)
|
|
if success:
|
|
return None
|
|
return self._docker_manager.get_last_delivery_error(bot_id) or "command delivery failed"
|
|
|
|
def get_recent_logs(self, *, bot_id: str, tail: int = 300) -> List[str]:
|
|
return list(self._docker_manager.get_recent_logs(bot_id, tail=tail) or [])
|
|
|
|
def ensure_monitor(self, *, bot_id: str) -> bool:
|
|
return bool(self._docker_manager.ensure_monitor(bot_id, self._on_state_change))
|
|
|
|
def get_monitor_packets(self, *, bot_id: str, after_seq: int = 0, limit: int = 200) -> List[Dict[str, Any]]:
|
|
return []
|
|
|
|
def get_runtime_status(self, *, bot_id: str) -> str:
|
|
return str(self._docker_manager.get_bot_status(bot_id) or "STOPPED").upper()
|
|
|
|
def get_resource_snapshot(self, *, bot_id: str) -> Dict[str, Any]:
|
|
return dict(self._docker_manager.get_bot_resource_snapshot(bot_id) or {})
|