初始化提交
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

This commit is contained in:
iven
2026-03-01 16:24:24 +08:00
commit 92e5def702
492 changed files with 211343 additions and 0 deletions

View File

@@ -0,0 +1,367 @@
"""
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")