ManusClaw-fixes / app /tool /platform_control.py
Jd Vijay
Upload app/tool/platform_control.py
40e2abf verified
Raw
History Blame Contribute Delete
11.2 kB
"""
PlatformControlTool β€” autonomous external platform manager.
Supports: GitHub, AWS, Vercel, WordPress, Netlify, HuggingFace,
Twitter/X, Telegram, Discord, generic REST.
"""
from __future__ import annotations
import json
from typing import Any, Optional
try:
import httpx
_HTTPX = True
except ImportError:
import urllib.request, urllib.error
_HTTPX = False
from app.tool.base import BaseTool, ToolResult
# ─────────────────────────────────────────────────────────────────────────────
# helpers
# ─────────────────────────────────────────────────────────────────────────────
def _req(method: str, url: str, headers: dict, params: dict = None,
json_body: Any = None, data: Any = None, timeout: int = 30) -> dict:
method = method.upper()
if _HTTPX:
with httpx.Client(timeout=timeout, follow_redirects=True) as c:
r = c.request(method, url, headers=headers,
params=params, json=json_body, data=data)
try:
return {"status": r.status_code, "body": r.json()}
except Exception:
return {"status": r.status_code, "body": r.text}
else:
import urllib.request, urllib.parse, json as _json
full_url = url + (("?" + urllib.parse.urlencode(params)) if params else "")
raw = _json.dumps(json_body).encode() if json_body else None
req = urllib.request.Request(full_url, data=raw, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
body = resp.read().decode()
try:
return {"status": resp.status, "body": _json.loads(body)}
except Exception:
return {"status": resp.status, "body": body}
except urllib.error.HTTPError as e:
return {"status": e.code, "body": e.read().decode()}
# ─────────────────────────────────────────────────────────────────────────────
# platform adapters
# ─────────────────────────────────────────────────────────────────────────────
class _GitHub:
BASE = "https://api.github.com"
def __init__(self, token: str):
self.h = {
"Authorization": f"token {token}",
"Accept": "application/vnd.github.v3+json",
"X-GitHub-Api-Version": "2022-11-28",
}
def call(self, method: str, path: str, params: dict = None,
body: Any = None) -> dict:
return _req(method, self.BASE + path, self.h, params, body)
class _Vercel:
BASE = "https://api.vercel.com"
def __init__(self, token: str, team_id: str = None):
self.h = {"Authorization": f"Bearer {token}"}
self.team = team_id
def call(self, method: str, path: str, params: dict = None,
body: Any = None) -> dict:
p = dict(params or {})
if self.team:
p["teamId"] = self.team
return _req(method, self.BASE + path, self.h, p or None, body)
class _WordPress:
def __init__(self, site_url: str, username: str, app_password: str):
import base64
creds = base64.b64encode(f"{username}:{app_password}".encode()).decode()
self.base = site_url.rstrip("/") + "/wp-json/wp/v2"
self.h = {"Authorization": f"Basic {creds}",
"Content-Type": "application/json"}
def call(self, method: str, endpoint: str, params: dict = None,
body: Any = None) -> dict:
return _req(method, self.base + endpoint, self.h, params, body)
class _HuggingFace:
BASE = "https://huggingface.co/api"
INFERENCE = "https://api-inference.huggingface.co"
def __init__(self, token: str):
self.h = {"Authorization": f"Bearer {token}"}
def call(self, method: str, path: str, params: dict = None,
body: Any = None) -> dict:
base = self.INFERENCE if path.startswith("/models") else self.BASE
return _req(method, base + path, self.h, params, body)
class _Netlify:
BASE = "https://api.netlify.com/api/v1"
def __init__(self, token: str):
self.h = {"Authorization": f"Bearer {token}"}
def call(self, method: str, path: str, params: dict = None,
body: Any = None) -> dict:
return _req(method, self.BASE + path, self.h, params, body)
class _Discord:
BASE = "https://discord.com/api/v10"
def __init__(self, bot_token: str):
self.h = {"Authorization": f"Bot {bot_token}",
"Content-Type": "application/json"}
def call(self, method: str, path: str, params: dict = None,
body: Any = None) -> dict:
return _req(method, self.BASE + path, self.h, params, body)
class _Telegram:
def __init__(self, bot_token: str):
self.base = f"https://api.telegram.org/bot{bot_token}"
def call(self, method: str, endpoint: str, params: dict = None,
body: Any = None) -> dict:
return _req(method, self.base + "/" + endpoint.lstrip("/"),
{}, params, body)
class _GenericREST:
def __init__(self, base_url: str, token: str = None,
auth_scheme: str = "Bearer", extra_headers: dict = None):
self.base = base_url.rstrip("/")
self.h = dict(extra_headers or {})
if token:
self.h["Authorization"] = f"{auth_scheme} {token}"
def call(self, method: str, path: str, params: dict = None,
body: Any = None) -> dict:
return _req(method, self.base + path, self.h, params, body)
# ─────────────────────────────────────────────────────────────────────────────
# platform registry
# ─────────────────────────────────────────────────────────────────────────────
def _build_platform(platform: str, credentials: dict):
p = platform.lower()
if p == "github":
return _GitHub(credentials["token"])
if p == "vercel":
return _Vercel(credentials["token"], credentials.get("team_id"))
if p in ("wordpress", "wp"):
return _WordPress(credentials["site_url"],
credentials["username"],
credentials["app_password"])
if p in ("huggingface", "hf"):
return _HuggingFace(credentials["token"])
if p == "netlify":
return _Netlify(credentials["token"])
if p == "discord":
return _Discord(credentials["bot_token"])
if p == "telegram":
return _Telegram(credentials["bot_token"])
return _GenericREST(
credentials.get("base_url", ""),
credentials.get("token"),
credentials.get("auth_scheme", "Bearer"),
credentials.get("headers"),
)
# ─────────────────────────────────────────────────────────────────────────────
# tool β€” FIXED: removed Pydantic Field, uses plain kwargs pattern matching BaseTool
# ─────────────────────────────────────────────────────────────────────────────
class PlatformControlTool(BaseTool):
"""
Authenticate with any external platform and perform any action.
Supported platforms (case-insensitive):
github | vercel | wordpress | huggingface | netlify | discord | telegram | generic
credentials dict keys by platform:
github β†’ token
vercel β†’ token, [team_id]
wordpress β†’ site_url, username, app_password
huggingface→ token
netlify β†’ token
discord β†’ bot_token
telegram β†’ bot_token
generic β†’ base_url, [token], [auth_scheme], [headers]
method: GET | POST | PUT | PATCH | DELETE
path: API path, e.g. /repos/owner/repo or /sites/{id}/deploys
params: URL query parameters (dict)
body: JSON request body (dict)
"""
name: str = "platform_control"
description: str = (
"Authenticate with and fully control any external platform "
"(GitHub, Vercel, WordPress, HuggingFace, Netlify, Discord, Telegram, "
"or any REST API). Can create, read, update, delete any resource "
"the provided credentials permit."
)
parameters: dict = {
"type": "object",
"properties": {
"platform": {
"type": "string",
"description": "Platform name, e.g. 'github', 'vercel', 'generic'.",
},
"credentials": {
"type": "object",
"description": "Auth credentials dict β€” see tool docstring for per-platform keys.",
},
"method": {
"type": "string",
"description": "HTTP method (GET, POST, PUT, PATCH, DELETE).",
"default": "GET",
},
"path": {
"type": "string",
"description": "API path to call.",
},
"params": {
"type": "object",
"description": "URL query params dict.",
},
"body": {
"type": "object",
"description": "JSON request body.",
},
},
"required": ["platform", "credentials", "path"],
}
async def execute(self, **kwargs: Any) -> ToolResult:
"""
Execute a platform control operation.
Reads from kwargs (standard BaseTool pattern, not Pydantic Field).
"""
platform = kwargs.get("platform")
credentials = kwargs.get("credentials", {})
method = kwargs.get("method", "GET")
path = kwargs.get("path", "")
params = kwargs.get("params")
body = kwargs.get("body")
if not platform:
return ToolResult(output="", error="platform is required")
try:
adapter = _build_platform(platform, credentials)
result = adapter.call(method, path, params, body)
return ToolResult(
output=json.dumps(result, indent=2, default=str),
error=None,
)
except Exception as e:
return ToolResult(output="", error=str(e))