chore: initial commit of ai site platform

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
whm
2026-07-31 10:19:22 +08:00
commit 6366859bb3
222 changed files with 47313 additions and 0 deletions

98
agent_sdk/client.py Normal file
View File

@@ -0,0 +1,98 @@
"""智能体侧:解密胶囊并按契约请求平台(前端不持有明文契约)。"""
from __future__ import annotations
import base64
import json
from typing import Any
import httpx
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
PREFIX = "AJZ1"
def decrypt_capsule(agent_key_b64: str, capsule: str) -> dict[str, Any]:
parts = capsule.split(".")
if len(parts) != 3 or parts[0] != PREFIX:
raise ValueError("invalid capsule")
key = base64.urlsafe_b64decode(pad(agent_key_b64))
nonce = base64.urlsafe_b64decode(pad(parts[1]))
data = base64.urlsafe_b64decode(pad(parts[2]))
# Go RawURLEncoding has no padding; Python needs pad
aes = AESGCM(key)
plain = aes.decrypt(nonce, data, None)
return json.loads(plain)
def pad(s: str) -> str:
return s + "=" * (-len(s) % 4)
class AgentClient:
def __init__(self, access_token: str, agent_key: str, capsule: str):
self.access_token = access_token
self.desc = decrypt_capsule(agent_key, capsule)
self.http = httpx.Client(
base_url=self.desc["base_url"],
headers={"Authorization": f"Bearer {access_token}"},
timeout=30.0,
)
def resources(self) -> list[dict[str, Any]]:
return self.desc.get("resources", [])
def list(self, resource_name: str, **filters: str) -> dict[str, Any]:
res = self._find(resource_name)
params = {f"filter.{k}": v for k, v in filters.items()}
r = self.http.get(res["path"], params=params)
r.raise_for_status()
return r.json()
def create(self, resource_name: str, body: dict[str, Any]) -> dict[str, Any]:
res = self._find(resource_name)
r = self.http.post(res["path"], json=body)
r.raise_for_status()
return r.json()
def get(self, resource_name: str, row_id: str) -> dict[str, Any]:
res = self._find(resource_name)
r = self.http.get(f"{res['path'].rstrip('/')}/{row_id}")
r.raise_for_status()
return r.json()
def update(self, resource_name: str, row_id: str, body: dict[str, Any]) -> dict[str, Any]:
res = self._find(resource_name)
r = self.http.put(f"{res['path'].rstrip('/')}/{row_id}", json=body)
r.raise_for_status()
return r.json()
def delete(self, resource_name: str, row_id: str) -> None:
res = self._find(resource_name)
r = self.http.delete(f"{res['path'].rstrip('/')}/{row_id}")
if r.status_code not in (200, 204):
r.raise_for_status()
def _find(self, name: str) -> dict[str, Any]:
for r in self.resources():
if r["name"] == name:
return r
raise KeyError(f"resource not in capsule: {name}")
if __name__ == "__main__":
import os
import sys
token = os.environ.get("AJZ_TOKEN", "")
key = os.environ.get("AJZ_AGENT_KEY", "")
capsule = os.environ.get("AJZ_CAPSULE", "")
if not (token and key and capsule):
print("Set AJZ_TOKEN / AJZ_AGENT_KEY / AJZ_CAPSULE", file=sys.stderr)
sys.exit(1)
client = AgentClient(token, key, capsule)
print(json.dumps({"app": client.desc["app_slug"], "resources": [r["name"] for r in client.resources()]}, ensure_ascii=False))
if client.resources():
name = client.resources()[0]["name"]
print(json.dumps(client.list(name), ensure_ascii=False, indent=2))

View File

@@ -0,0 +1,2 @@
httpx==0.28.1
cryptography==44.0.0