from typing import List, Optional from fastapi import APIRouter, Depends, File, HTTPException, Query, Request, UploadFile from app.dependencies.auth import require_edge_auth from app.schemas.edge import ( EdgeCommandRequest, EdgeLogsResponse, EdgeNativePreflightRequest, EdgeNativePreflightResponse, EdgeNodeHeartbeatResponse, EdgeMonitorPacketsResponse, EdgeMarkdownWriteRequest, EdgeMonitorEnsureResponse, EdgeNodeResourcesResponse, EdgeNodeSelfResponse, EdgeStateResponse, EdgeStateWriteRequest, EdgeStatusResponse, EdgeWorkspaceSyncRequest, ) from app.schemas.runtime import EdgeStartBotRequest from app.services import provision_service as provision_service_module from app.services import state_store_service as state_store_service_module from app.services.runtime_service import edge_runtime_service from app.services import workspace_service as workspace_service_module router = APIRouter(dependencies=[Depends(require_edge_auth)]) @router.get("/api/edge/node/self", response_model=EdgeNodeSelfResponse) def get_edge_node_self(): return edge_runtime_service.get_node_identity() @router.get("/api/edge/node/resources", response_model=EdgeNodeResourcesResponse) def get_edge_node_resources(): return edge_runtime_service.get_node_resource_summary() @router.post("/api/edge/node/heartbeat", response_model=EdgeNodeHeartbeatResponse) def heartbeat_edge_node(): return edge_runtime_service.heartbeat() @router.post("/api/edge/runtime/native/preflight", response_model=EdgeNativePreflightResponse) def native_preflight(payload: EdgeNativePreflightRequest): return edge_runtime_service.native_preflight( native_command=str(payload.native_command or "").strip() or None, native_workdir=str(payload.native_workdir or "").strip() or None, ) @router.post("/api/edge/bots/{bot_id}/start", response_model=EdgeStatusResponse) async def start_bot(bot_id: str, payload: EdgeStartBotRequest): return await edge_runtime_service.start_bot(bot_id=bot_id, payload=payload) @router.post("/api/edge/bots/{bot_id}/stop", response_model=EdgeStatusResponse) def stop_bot(bot_id: str): return edge_runtime_service.stop_bot(bot_id=bot_id) @router.post("/api/edge/bots/{bot_id}/command", response_model=EdgeStatusResponse) def send_command(bot_id: str, payload: EdgeCommandRequest): return edge_runtime_service.send_command(bot_id=bot_id, payload=payload) @router.post("/api/edge/bots/{bot_id}/monitor/ensure", response_model=EdgeMonitorEnsureResponse) def ensure_monitor(bot_id: str): return edge_runtime_service.ensure_monitor(bot_id=bot_id) @router.get("/api/edge/bots/{bot_id}/monitor/packets", response_model=EdgeMonitorPacketsResponse) def get_monitor_packets(bot_id: str, after_seq: int = 0, limit: int = 200): return edge_runtime_service.get_monitor_packets(bot_id=bot_id, after_seq=after_seq, limit=limit) @router.get("/api/edge/bots/{bot_id}/logs", response_model=EdgeLogsResponse) def get_logs(bot_id: str, tail: int = Query(300, ge=1, le=2000)): return edge_runtime_service.get_recent_logs(bot_id=bot_id, tail=tail) @router.get("/api/edge/bots/{bot_id}/runtime/status", response_model=EdgeStatusResponse) def get_runtime_status(bot_id: str): return edge_runtime_service.get_runtime_status(bot_id=bot_id) @router.get("/api/edge/bots/{bot_id}/resources") def get_resource_snapshot(bot_id: str): return edge_runtime_service.get_resource_snapshot(bot_id=bot_id) @router.post("/api/edge/bots/{bot_id}/workspace/sync", response_model=EdgeStatusResponse) def sync_workspace(bot_id: str, payload: EdgeWorkspaceSyncRequest): return provision_service_module.edge_provision_service.sync_bot_workspace(bot_id=bot_id, payload=payload) @router.get("/api/edge/bots/{bot_id}/state/{state_key}", response_model=EdgeStateResponse) def read_bot_state(bot_id: str, state_key: str, workspace_root: str | None = None): return state_store_service_module.edge_state_store_service.read_state( bot_id=bot_id, state_key=state_key, workspace_root=workspace_root, ) @router.put("/api/edge/bots/{bot_id}/state/{state_key}", response_model=EdgeStateResponse) def write_bot_state(bot_id: str, state_key: str, payload: EdgeStateWriteRequest): return state_store_service_module.edge_state_store_service.write_state( bot_id=bot_id, state_key=state_key, data=dict(payload.data or {}), workspace_root=str(payload.workspace_root or "").strip() or None, ) @router.get("/api/edge/bots/{bot_id}/workspace/tree") def list_workspace_tree( bot_id: str, path: str | None = None, recursive: bool = False, workspace_root: str | None = None, ): return workspace_service_module.edge_workspace_service.list_tree( bot_id=bot_id, path=path, recursive=recursive, workspace_root=workspace_root, ) @router.get("/api/edge/bots/{bot_id}/workspace/file") def read_workspace_file( bot_id: str, path: str = Query(...), max_bytes: int = Query(200000, ge=4096, le=1000000), workspace_root: str | None = None, ): return workspace_service_module.edge_workspace_service.read_file( bot_id=bot_id, path=path, max_bytes=max_bytes, workspace_root=workspace_root, ) @router.put("/api/edge/bots/{bot_id}/workspace/file/markdown") def write_workspace_markdown( bot_id: str, path: str = Query(...), payload: EdgeMarkdownWriteRequest = None, workspace_root: str | None = None, ): if payload is None: raise HTTPException(status_code=400, detail="markdown payload is required") return workspace_service_module.edge_workspace_service.write_markdown( bot_id=bot_id, path=path, content=payload.content, workspace_root=workspace_root, ) @router.post("/api/edge/bots/{bot_id}/workspace/upload") async def upload_workspace_files( bot_id: str, files: List[UploadFile] = File(...), path: Optional[str] = None, workspace_root: str | None = None, ): return await workspace_service_module.edge_workspace_service.upload_files( bot_id=bot_id, files=files, path=path, workspace_root=workspace_root, ) @router.get("/api/edge/bots/{bot_id}/workspace/download") def download_workspace_file( bot_id: str, path: str = Query(...), download: bool = False, request: Request = None, workspace_root: str | None = None, ): return workspace_service_module.edge_workspace_service.serve_file( bot_id=bot_id, path=path, download=download, request=request, workspace_root=workspace_root, ) @router.get("/api/edge/bots/{bot_id}/workspace/raw/{path:path}") def raw_workspace_file( bot_id: str, path: str, download: bool = False, request: Request = None, workspace_root: str | None = None, ): return workspace_service_module.edge_workspace_service.serve_file( bot_id=bot_id, path=path, download=download, request=request, workspace_root=workspace_root, ) @router.post("/api/edge/bots/{bot_id}/workspace/purge", response_model=EdgeStatusResponse) def purge_workspace(bot_id: str, workspace_root: str | None = None): result = workspace_service_module.edge_workspace_service.purge_bot_workspace( bot_id=bot_id, workspace_root=workspace_root, ) return EdgeStatusResponse(status="deleted" if bool(result.get("deleted")) else "not_found")