Some checks failed
CI / Check / macos-latest (push) Has been cancelled
CI / Check / ubuntu-latest (push) Has been cancelled
CI / Check / windows-latest (push) Has been cancelled
CI / Test / macos-latest (push) Has been cancelled
CI / Test / ubuntu-latest (push) Has been cancelled
CI / Test / windows-latest (push) Has been cancelled
CI / Clippy (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / Secrets Scan (push) Has been cancelled
CI / Install Script Smoke Test (push) Has been cancelled
368 lines
13 KiB
Python
368 lines
13 KiB
Python
"""
|
|
OpenFang Python Client — REST API client for controlling OpenFang remotely.
|
|
|
|
Usage:
|
|
|
|
from openfang_client import OpenFang
|
|
|
|
client = OpenFang("http://localhost:3000")
|
|
|
|
# Create an agent
|
|
agent = client.agents.create(template="assistant")
|
|
print(agent["id"])
|
|
|
|
# Send a message
|
|
reply = client.agents.message(agent["id"], "Hello!")
|
|
print(reply)
|
|
|
|
# Stream a response
|
|
for event in client.agents.stream(agent["id"], "Tell me a joke"):
|
|
if event.get("type") == "text_delta":
|
|
print(event["delta"], end="", flush=True)
|
|
|
|
Note: This is the REST API *client* library.
|
|
For writing Python agents that run inside OpenFang, see openfang_sdk.py instead.
|
|
"""
|
|
|
|
import json
|
|
from typing import Any, Dict, Generator, Optional
|
|
from urllib.request import urlopen, Request
|
|
from urllib.error import HTTPError
|
|
from urllib.parse import urlencode, quote
|
|
|
|
|
|
class OpenFangError(Exception):
|
|
def __init__(self, message: str, status: int = 0, body: str = ""):
|
|
super().__init__(message)
|
|
self.status = status
|
|
self.body = body
|
|
|
|
|
|
class _Resource:
|
|
def __init__(self, client: "OpenFang"):
|
|
self._c = client
|
|
|
|
|
|
class OpenFang:
|
|
"""OpenFang REST API client. Zero dependencies — uses only stdlib urllib."""
|
|
|
|
def __init__(self, base_url: str, headers: Optional[Dict[str, str]] = None):
|
|
self.base_url = base_url.rstrip("/")
|
|
self._headers = {"Content-Type": "application/json"}
|
|
if headers:
|
|
self._headers.update(headers)
|
|
|
|
self.agents = _AgentResource(self)
|
|
self.sessions = _SessionResource(self)
|
|
self.workflows = _WorkflowResource(self)
|
|
self.skills = _SkillResource(self)
|
|
self.channels = _ChannelResource(self)
|
|
self.tools = _ToolResource(self)
|
|
self.models = _ModelResource(self)
|
|
self.providers = _ProviderResource(self)
|
|
self.memory = _MemoryResource(self)
|
|
self.triggers = _TriggerResource(self)
|
|
self.schedules = _ScheduleResource(self)
|
|
|
|
def _request(self, method: str, path: str, body: Any = None) -> Any:
|
|
url = self.base_url + path
|
|
data = json.dumps(body).encode() if body is not None else None
|
|
req = Request(url, data=data, headers=self._headers, method=method)
|
|
try:
|
|
with urlopen(req) as resp:
|
|
ct = resp.headers.get("content-type", "")
|
|
text = resp.read().decode()
|
|
if "application/json" in ct:
|
|
return json.loads(text)
|
|
return text
|
|
except HTTPError as e:
|
|
body_text = e.read().decode() if e.fp else ""
|
|
raise OpenFangError(f"HTTP {e.code}: {body_text}", e.code, body_text) from e
|
|
|
|
def _stream(self, method: str, path: str, body: Any = None) -> Generator[Dict, None, None]:
|
|
"""SSE streaming. Yields parsed JSON events."""
|
|
url = self.base_url + path
|
|
data = json.dumps(body).encode() if body is not None else None
|
|
headers = dict(self._headers)
|
|
headers["Accept"] = "text/event-stream"
|
|
req = Request(url, data=data, headers=headers, method=method)
|
|
try:
|
|
resp = urlopen(req)
|
|
except HTTPError as e:
|
|
body_text = e.read().decode() if e.fp else ""
|
|
raise OpenFangError(f"HTTP {e.code}: {body_text}", e.code, body_text) from e
|
|
|
|
buffer = ""
|
|
while True:
|
|
chunk = resp.read(4096)
|
|
if not chunk:
|
|
break
|
|
buffer += chunk.decode()
|
|
lines = buffer.split("\n")
|
|
buffer = lines.pop()
|
|
for line in lines:
|
|
line = line.strip()
|
|
if line.startswith("data: "):
|
|
data_str = line[6:]
|
|
if data_str == "[DONE]":
|
|
return
|
|
try:
|
|
yield json.loads(data_str)
|
|
except json.JSONDecodeError:
|
|
yield {"raw": data_str}
|
|
resp.close()
|
|
|
|
def health(self) -> Any:
|
|
return self._request("GET", "/api/health")
|
|
|
|
def health_detail(self) -> Any:
|
|
return self._request("GET", "/api/health/detail")
|
|
|
|
def status(self) -> Any:
|
|
return self._request("GET", "/api/status")
|
|
|
|
def version(self) -> Any:
|
|
return self._request("GET", "/api/version")
|
|
|
|
def metrics(self) -> str:
|
|
return self._request("GET", "/api/metrics")
|
|
|
|
def usage(self) -> Any:
|
|
return self._request("GET", "/api/usage")
|
|
|
|
def config(self) -> Any:
|
|
return self._request("GET", "/api/config")
|
|
|
|
|
|
# ── Agent Resource ──────────────────────────────────────────────
|
|
|
|
class _AgentResource(_Resource):
|
|
|
|
def list(self):
|
|
return self._c._request("GET", "/api/agents")
|
|
|
|
def get(self, agent_id: str):
|
|
return self._c._request("GET", f"/api/agents/{agent_id}")
|
|
|
|
def create(self, **kwargs):
|
|
return self._c._request("POST", "/api/agents", kwargs)
|
|
|
|
def delete(self, agent_id: str):
|
|
return self._c._request("DELETE", f"/api/agents/{agent_id}")
|
|
|
|
def stop(self, agent_id: str):
|
|
return self._c._request("POST", f"/api/agents/{agent_id}/stop")
|
|
|
|
def clone(self, agent_id: str):
|
|
return self._c._request("POST", f"/api/agents/{agent_id}/clone")
|
|
|
|
def update(self, agent_id: str, **data):
|
|
return self._c._request("PUT", f"/api/agents/{agent_id}/update", data)
|
|
|
|
def set_mode(self, agent_id: str, mode: str):
|
|
return self._c._request("PUT", f"/api/agents/{agent_id}/mode", {"mode": mode})
|
|
|
|
def set_model(self, agent_id: str, model: str):
|
|
return self._c._request("PUT", f"/api/agents/{agent_id}/model", {"model": model})
|
|
|
|
def message(self, agent_id: str, text: str, **opts):
|
|
body = {"message": text, **opts}
|
|
return self._c._request("POST", f"/api/agents/{agent_id}/message", body)
|
|
|
|
def stream(self, agent_id: str, text: str, **opts) -> Generator[Dict, None, None]:
|
|
"""Stream response events. Usage:
|
|
for event in client.agents.stream(id, "Hello"):
|
|
if event.get("type") == "text_delta":
|
|
print(event["delta"], end="")
|
|
"""
|
|
body = {"message": text, **opts}
|
|
return self._c._stream("POST", f"/api/agents/{agent_id}/message/stream", body)
|
|
|
|
def session(self, agent_id: str):
|
|
return self._c._request("GET", f"/api/agents/{agent_id}/session")
|
|
|
|
def reset_session(self, agent_id: str):
|
|
return self._c._request("POST", f"/api/agents/{agent_id}/session/reset")
|
|
|
|
def compact_session(self, agent_id: str):
|
|
return self._c._request("POST", f"/api/agents/{agent_id}/session/compact")
|
|
|
|
def list_sessions(self, agent_id: str):
|
|
return self._c._request("GET", f"/api/agents/{agent_id}/sessions")
|
|
|
|
def create_session(self, agent_id: str, label: Optional[str] = None):
|
|
return self._c._request("POST", f"/api/agents/{agent_id}/sessions", {"label": label})
|
|
|
|
def switch_session(self, agent_id: str, session_id: str):
|
|
return self._c._request("POST", f"/api/agents/{agent_id}/sessions/{session_id}/switch")
|
|
|
|
def get_skills(self, agent_id: str):
|
|
return self._c._request("GET", f"/api/agents/{agent_id}/skills")
|
|
|
|
def set_skills(self, agent_id: str, skills):
|
|
return self._c._request("PUT", f"/api/agents/{agent_id}/skills", skills)
|
|
|
|
def set_identity(self, agent_id: str, **identity):
|
|
return self._c._request("PATCH", f"/api/agents/{agent_id}/identity", identity)
|
|
|
|
def patch_config(self, agent_id: str, **config):
|
|
return self._c._request("PATCH", f"/api/agents/{agent_id}/config", config)
|
|
|
|
|
|
# ── Session Resource ────────────────────────────────────────────
|
|
|
|
class _SessionResource(_Resource):
|
|
|
|
def list(self):
|
|
return self._c._request("GET", "/api/sessions")
|
|
|
|
def delete(self, session_id: str):
|
|
return self._c._request("DELETE", f"/api/sessions/{session_id}")
|
|
|
|
def set_label(self, session_id: str, label: str):
|
|
return self._c._request("PUT", f"/api/sessions/{session_id}/label", {"label": label})
|
|
|
|
|
|
# ── Workflow Resource ───────────────────────────────────────────
|
|
|
|
class _WorkflowResource(_Resource):
|
|
|
|
def list(self):
|
|
return self._c._request("GET", "/api/workflows")
|
|
|
|
def create(self, **workflow):
|
|
return self._c._request("POST", "/api/workflows", workflow)
|
|
|
|
def run(self, workflow_id: str, input_data=None):
|
|
return self._c._request("POST", f"/api/workflows/{workflow_id}/run", input_data)
|
|
|
|
def runs(self, workflow_id: str):
|
|
return self._c._request("GET", f"/api/workflows/{workflow_id}/runs")
|
|
|
|
|
|
# ── Skill Resource ──────────────────────────────────────────────
|
|
|
|
class _SkillResource(_Resource):
|
|
|
|
def list(self):
|
|
return self._c._request("GET", "/api/skills")
|
|
|
|
def install(self, **skill):
|
|
return self._c._request("POST", "/api/skills/install", skill)
|
|
|
|
def uninstall(self, **skill):
|
|
return self._c._request("POST", "/api/skills/uninstall", skill)
|
|
|
|
def search(self, query: str):
|
|
return self._c._request("GET", f"/api/marketplace/search?q={quote(query)}")
|
|
|
|
|
|
# ── Channel Resource ────────────────────────────────────────────
|
|
|
|
class _ChannelResource(_Resource):
|
|
|
|
def list(self):
|
|
return self._c._request("GET", "/api/channels")
|
|
|
|
def configure(self, name: str, **config):
|
|
return self._c._request("POST", f"/api/channels/{name}/configure", config)
|
|
|
|
def remove(self, name: str):
|
|
return self._c._request("DELETE", f"/api/channels/{name}/configure")
|
|
|
|
def test(self, name: str):
|
|
return self._c._request("POST", f"/api/channels/{name}/test")
|
|
|
|
|
|
# ── Tool Resource ───────────────────────────────────────────────
|
|
|
|
class _ToolResource(_Resource):
|
|
|
|
def list(self):
|
|
return self._c._request("GET", "/api/tools")
|
|
|
|
|
|
# ── Model Resource ──────────────────────────────────────────────
|
|
|
|
class _ModelResource(_Resource):
|
|
|
|
def list(self):
|
|
return self._c._request("GET", "/api/models")
|
|
|
|
def get(self, model_id: str):
|
|
return self._c._request("GET", f"/api/models/{model_id}")
|
|
|
|
def aliases(self):
|
|
return self._c._request("GET", "/api/models/aliases")
|
|
|
|
|
|
# ── Provider Resource ───────────────────────────────────────────
|
|
|
|
class _ProviderResource(_Resource):
|
|
|
|
def list(self):
|
|
return self._c._request("GET", "/api/providers")
|
|
|
|
def set_key(self, name: str, key: str):
|
|
return self._c._request("POST", f"/api/providers/{name}/key", {"key": key})
|
|
|
|
def delete_key(self, name: str):
|
|
return self._c._request("DELETE", f"/api/providers/{name}/key")
|
|
|
|
def test(self, name: str):
|
|
return self._c._request("POST", f"/api/providers/{name}/test")
|
|
|
|
|
|
# ── Memory Resource ─────────────────────────────────────────────
|
|
|
|
class _MemoryResource(_Resource):
|
|
|
|
def get_all(self, agent_id: str):
|
|
return self._c._request("GET", f"/api/memory/agents/{agent_id}/kv")
|
|
|
|
def get(self, agent_id: str, key: str):
|
|
return self._c._request("GET", f"/api/memory/agents/{agent_id}/kv/{key}")
|
|
|
|
def set(self, agent_id: str, key: str, value):
|
|
return self._c._request("PUT", f"/api/memory/agents/{agent_id}/kv/{key}", {"value": value})
|
|
|
|
def delete(self, agent_id: str, key: str):
|
|
return self._c._request("DELETE", f"/api/memory/agents/{agent_id}/kv/{key}")
|
|
|
|
|
|
# ── Trigger Resource ────────────────────────────────────────────
|
|
|
|
class _TriggerResource(_Resource):
|
|
|
|
def list(self):
|
|
return self._c._request("GET", "/api/triggers")
|
|
|
|
def create(self, **trigger):
|
|
return self._c._request("POST", "/api/triggers", trigger)
|
|
|
|
def update(self, trigger_id: str, **trigger):
|
|
return self._c._request("PUT", f"/api/triggers/{trigger_id}", trigger)
|
|
|
|
def delete(self, trigger_id: str):
|
|
return self._c._request("DELETE", f"/api/triggers/{trigger_id}")
|
|
|
|
|
|
# ── Schedule Resource ───────────────────────────────────────────
|
|
|
|
class _ScheduleResource(_Resource):
|
|
|
|
def list(self):
|
|
return self._c._request("GET", "/api/schedules")
|
|
|
|
def create(self, **schedule):
|
|
return self._c._request("POST", "/api/schedules", schedule)
|
|
|
|
def update(self, schedule_id: str, **schedule):
|
|
return self._c._request("PUT", f"/api/schedules/{schedule_id}", schedule)
|
|
|
|
def delete(self, schedule_id: str):
|
|
return self._c._request("DELETE", f"/api/schedules/{schedule_id}")
|
|
|
|
def run(self, schedule_id: str):
|
|
return self._c._request("POST", f"/api/schedules/{schedule_id}/run")
|