Files
Elydora-Python-SDK/elydora/_client_common.py
T
雷电芽衣 0f72a5e2f1 chore(python): audit core package
_client_common.py owns request building and EOR signing for both clients; types follow the Backend contract; the deprecated issue_token alias and token response aliases are removed; crypto, utils, and CLI comments trimmed.

Claude-Session: https://claude.ai/code/session_01Agoc9bRVE9KL7mmWtey9Vq
2026-09-02 23:05:37 -04:00

116 lines
3.3 KiB
Python

"""Request and response building shared by the sync and async clients."""
from __future__ import annotations
import time
from typing import Any, Dict, Optional, Tuple, Union
from .crypto import compute_chain_hash, compute_payload_hash, sign_eor
from .errors import ElydoraError
from .types import EOR
from .utils import generate_nonce, generate_uuidv7
# 32 zero bytes; equals the Backend GENESIS_CHAIN_HASH.
GENESIS_CHAIN_HASH = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
DEFAULT_BASE_URL = "https://api.elydora.com"
REQUEST_TIMEOUT_SECONDS = 30
def build_headers(token: Optional[str]) -> Dict[str, str]:
headers = {"Content-Type": "application/json"}
if token:
headers["Authorization"] = f"Bearer {token}"
return headers
def error_from_response(status_code: int, body: Any, text: str) -> ElydoraError:
error = body.get("error", {}) if isinstance(body, dict) else {}
return ElydoraError(
code=error.get("code", "INTERNAL_ERROR"),
message=error.get("message") or text or "Unknown error",
request_id=error.get("request_id", ""),
details=error.get("details"),
status_code=status_code,
)
def without_none(values: Dict[str, Any]) -> Dict[str, Any]:
return {key: value for key, value in values.items() if value is not None}
def register_body(
email: str,
password: str,
display_name: Optional[str],
org_name: Optional[str],
) -> Dict[str, Any]:
return without_none({
"email": email,
"password": password,
"display_name": display_name,
"org_name": org_name,
})
def api_token_body(ttl_seconds: Optional[int]) -> Dict[str, Any]:
return without_none({"ttl_seconds": ttl_seconds})
def export_body(
start_time: int,
end_time: int,
format: str,
agent_id: Optional[str],
operation_type: Optional[str],
) -> Dict[str, Any]:
return without_none({
"start_time": start_time,
"end_time": end_time,
"format": format,
"agent_id": agent_id,
"operation_type": operation_type,
})
def admin_events_params(limit: Optional[int]) -> Dict[str, str]:
return {} if limit is None else {"limit": str(limit)}
def build_eor(
*,
org_id: str,
agent_id: str,
private_key: str,
kid: str,
ttl_ms: int,
prev_chain_hash: str,
operation_type: str,
subject: Dict[str, Any],
action: Dict[str, Any],
payload: Union[Dict[str, Any], str, None],
) -> Tuple[EOR, str]:
"""Build and sign one EOR; returns the record and its chain hash."""
operation_id = generate_uuidv7()
issued_at = int(time.time() * 1000)
payload_hash = compute_payload_hash(payload)
chain_hash = compute_chain_hash(prev_chain_hash, payload_hash, operation_id, issued_at)
eor: Dict[str, Any] = {
"op_version": "1.0",
"operation_id": operation_id,
"org_id": org_id,
"agent_id": agent_id,
"issued_at": issued_at,
"ttl_ms": ttl_ms,
"nonce": generate_nonce(),
"operation_type": operation_type,
"subject": subject,
"action": action,
"payload": payload,
"payload_hash": payload_hash,
"prev_chain_hash": prev_chain_hash,
"agent_pubkey_kid": kid,
"signature": "",
}
eor["signature"] = sign_eor(eor, private_key)
return eor, chain_hash # type: ignore[return-value]