PYAE1994's picture
feat: GOD MODE+ v4.0 - api/routes/execution.py
9f88063 verified
"""
Execution API Routes β€” Terminal, FileSystem, GitHub, DAG
Real agent execution endpoints
"""
import asyncio
import json
import os
import time
import uuid
from typing import Dict, List, Optional
import structlog
from fastapi import APIRouter, HTTPException, Request, WebSocket, WebSocketDisconnect
from pydantic import BaseModel
log = structlog.get_logger()
router = APIRouter()
WORKSPACE = os.environ.get("WORKSPACE_DIR", "/tmp/god_workspace")
# ─── Request Models ───────────────────────────────────────────────────────────
class ExecRequest(BaseModel):
command: str
session_id: str = ""
task_id: str = ""
cwd: str = ""
timeout: int = 120
class ExecChainRequest(BaseModel):
commands: List[str]
session_id: str = ""
task_id: str = ""
cwd: str = ""
stop_on_error: bool = True
class FileReadRequest(BaseModel):
filename: str
class FileWriteRequest(BaseModel):
filename: str
content: str
class FilePatchRequest(BaseModel):
filename: str
old_str: str
new_str: str
class FileDeleteRequest(BaseModel):
filename: str
class FileMoveRequest(BaseModel):
src: str
dst: str
class FileSearchRequest(BaseModel):
query: str
path: str = ""
pattern: str = "*"
class GitHubCloneRequest(BaseModel):
repo_url: str
dest: str = ""
branch: str = ""
class GitHubCreateRepoRequest(BaseModel):
name: str
description: str = ""
private: bool = False
class GitHubCommitRequest(BaseModel):
message: str
cwd: str = ""
files: Optional[List[str]] = None
class GitHubPushRequest(BaseModel):
branch: str = ""
cwd: str = ""
force: bool = False
class GitHubPRRequest(BaseModel):
owner: str
repo: str
title: str
body: str = ""
head: str = "main"
base: str = "main"
class DAGCreateRequest(BaseModel):
goal: str
steps: List[Dict]
session_id: str = ""
class FilesWriteRequest(BaseModel):
files: List[Dict]
# ─── Terminal Routes ──────────────────────────────────────────────────────────
@router.post("/execute")
async def execute_command(req: ExecRequest, request: Request):
"""Execute a shell command in the sandbox."""
terminal = getattr(request.app.state, "terminal_engine", None)
if not terminal:
return {"success": False, "error": "Terminal engine not available"}
result = await terminal.execute(
req.command,
session_id=req.session_id,
task_id=req.task_id,
cwd=req.cwd or WORKSPACE,
timeout=req.timeout,
)
return result
@router.post("/execute/chain")
async def execute_chain(req: ExecChainRequest, request: Request):
"""Execute a chain of shell commands."""
terminal = getattr(request.app.state, "terminal_engine", None)
if not terminal:
return {"success": False, "error": "Terminal engine not available"}
result = await terminal.execute_chain(
req.commands,
session_id=req.session_id,
task_id=req.task_id,
cwd=req.cwd or WORKSPACE,
stop_on_error=req.stop_on_error,
)
return result
@router.post("/execute/kill/{session_id}")
async def kill_process(session_id: str, request: Request):
"""Kill a running process."""
terminal = getattr(request.app.state, "terminal_engine", None)
if not terminal:
return {"success": False, "error": "Terminal engine not available"}
return await terminal.kill(session_id)
@router.get("/execute/history/{session_id}")
async def get_history(session_id: str, request: Request):
"""Get command history for a session."""
terminal = getattr(request.app.state, "terminal_engine", None)
if not terminal:
return {"history": []}
return {"history": terminal.get_session_history(session_id)}
# ─── FileSystem Routes ────────────────────────────────────────────────────────
@router.post("/fs/read")
async def read_file(req: FileReadRequest, request: Request):
fs = getattr(request.app.state, "fs_tool", None)
if not fs:
return {"success": False, "error": "FileSystem tool not available"}
return await fs.read_file(req.filename)
@router.post("/fs/write")
async def write_file(req: FileWriteRequest, request: Request):
fs = getattr(request.app.state, "fs_tool", None)
if not fs:
return {"success": False, "error": "FileSystem tool not available"}
return await fs.write_file(req.filename, req.content)
@router.post("/fs/write-many")
async def write_files(req: FilesWriteRequest, request: Request):
fs = getattr(request.app.state, "fs_tool", None)
if not fs:
return {"success": False, "error": "FileSystem tool not available"}
return await fs.write_files(req.files)
@router.post("/fs/patch")
async def patch_file(req: FilePatchRequest, request: Request):
fs = getattr(request.app.state, "fs_tool", None)
if not fs:
return {"success": False, "error": "FileSystem tool not available"}
return await fs.patch_file(req.filename, req.old_str, req.new_str)
@router.post("/fs/delete")
async def delete_file(req: FileDeleteRequest, request: Request):
fs = getattr(request.app.state, "fs_tool", None)
if not fs:
return {"success": False, "error": "FileSystem tool not available"}
return await fs.delete_file(req.filename)
@router.post("/fs/move")
async def move_file(req: FileMoveRequest, request: Request):
fs = getattr(request.app.state, "fs_tool", None)
if not fs:
return {"success": False, "error": "FileSystem tool not available"}
return await fs.move_file(req.src, req.dst)
@router.post("/fs/search")
async def search_files(req: FileSearchRequest, request: Request):
fs = getattr(request.app.state, "fs_tool", None)
if not fs:
return {"success": False, "error": "FileSystem tool not available"}
return await fs.search_files(req.query, req.path, req.pattern)
@router.get("/fs/tree")
async def get_tree(path: str = "", request: Request = None):
fs = getattr(request.app.state, "fs_tool", None)
if not fs:
return {"success": False, "error": "FileSystem tool not available"}
return await fs.tree(path)
@router.get("/fs/list")
async def list_dir(path: str = "", request: Request = None):
fs = getattr(request.app.state, "fs_tool", None)
if not fs:
return {"success": False, "error": "FileSystem tool not available"}
return await fs.list_dir(path)
@router.get("/workspace")
async def get_workspace_info(request: Request):
"""Get workspace info and file tree."""
fs = getattr(request.app.state, "fs_tool", None)
if not fs:
return {"path": WORKSPACE, "files": [], "file_count": 0}
tree = await fs.tree()
files_list = []
for root, dirs, files in os.walk(WORKSPACE):
dirs[:] = [d for d in dirs if not d.startswith(".") and d not in ("node_modules", "__pycache__")]
for f in files:
files_list.append(os.path.relpath(os.path.join(root, f), WORKSPACE))
if len(files_list) > 200:
break
return {
"path": WORKSPACE,
"file_count": len(files_list),
"files": files_list[:100],
"tree": tree.get("tree", ""),
}
# ─── GitHub Routes ────────────────────────────────────────────────────────────
@router.post("/github/clone")
async def github_clone(req: GitHubCloneRequest, request: Request):
gh = getattr(request.app.state, "github_tool", None)
if not gh:
return {"success": False, "error": "GitHub tool not available"}
return await gh.clone_repo(req.repo_url, req.dest, req.branch)
@router.post("/github/create-repo")
async def github_create_repo(req: GitHubCreateRepoRequest, request: Request):
gh = getattr(request.app.state, "github_tool", None)
if not gh:
return {"success": False, "error": "GitHub tool not available"}
return await gh.create_repo(req.name, req.description, req.private)
@router.post("/github/commit")
async def github_commit(req: GitHubCommitRequest, request: Request):
gh = getattr(request.app.state, "github_tool", None)
if not gh:
return {"success": False, "error": "GitHub tool not available"}
return await gh.commit_changes(req.message, req.cwd or WORKSPACE, req.files)
@router.post("/github/push")
async def github_push(req: GitHubPushRequest, request: Request):
gh = getattr(request.app.state, "github_tool", None)
if not gh:
return {"success": False, "error": "GitHub tool not available"}
return await gh.push_changes(req.branch, req.cwd or WORKSPACE, req.force)
@router.post("/github/pr")
async def github_open_pr(req: GitHubPRRequest, request: Request):
gh = getattr(request.app.state, "github_tool", None)
if not gh:
return {"success": False, "error": "GitHub tool not available"}
return await gh.open_pr(req.owner, req.repo, req.title, req.body, req.head, req.base)
@router.get("/github/status")
async def github_status(cwd: str = "", request: Request = None):
gh = getattr(request.app.state, "github_tool", None)
if not gh:
return {"success": False, "error": "GitHub tool not available"}
return await gh.status(cwd or WORKSPACE)
@router.get("/github/issues/{owner}/{repo}")
async def github_issues(owner: str, repo: str, request: Request):
gh = getattr(request.app.state, "github_tool", None)
if not gh:
return {"success": False, "error": "GitHub tool not available"}
return await gh.read_issues(owner, repo)
# ─── DAG Routes ───────────────────────────────────────────────────────────────
@router.post("/dag/create")
async def create_dag(req: DAGCreateRequest, request: Request):
dag_engine = getattr(request.app.state, "dag_engine", None)
if not dag_engine:
return {"success": False, "error": "DAG engine not available"}
dag = dag_engine.build_from_steps(req.steps, req.goal)
return {"success": True, "dag_id": dag.id, "dag": dag.to_dict()}
@router.get("/dag/list")
async def list_dags(request: Request):
dag_engine = getattr(request.app.state, "dag_engine", None)
if not dag_engine:
return {"dags": []}
return {"dags": dag_engine.get_all_dags()}
@router.get("/dag/{dag_id}")
async def get_dag(dag_id: str, request: Request):
dag_engine = getattr(request.app.state, "dag_engine", None)
if not dag_engine:
return {"success": False, "error": "DAG engine not available"}
dag = dag_engine.get_dag(dag_id)
if not dag:
raise HTTPException(status_code=404, detail="DAG not found")
return dag.to_dict()
# ─── Self-Repair Route ────────────────────────────────────────────────────────
class SelfRepairRequest(BaseModel):
command: str
error_output: str
session_id: str = ""
task_id: str = ""
max_attempts: int = 3
@router.post("/self-repair")
async def self_repair(req: SelfRepairRequest, request: Request):
terminal = getattr(request.app.state, "terminal_engine", None)
ai_router = getattr(request.app.state, "ai_router", None)
if not terminal:
return {"success": False, "error": "Terminal engine not available"}
return await terminal.self_repair(
req.command,
req.error_output,
ai_router=ai_router,
session_id=req.session_id,
task_id=req.task_id,
max_attempts=req.max_attempts,
)