"""GLM adapter (Zhipu bigmodel, OpenAI-compatible chat endpoint) — the first real engine.
Key handling (GOV-B7.3): GLM_API_KEY is read from the environment at CALL time. It is
never written to any file under static/, never embedded in any response, never logged.
Every outbound call goes through guarded_post_json(): scheme and host validated, all
resolved IPs public, connection pinned to the validated IP (no DNS rebinding), TLS by
hostname, redirects refused.
"""
import json
from .base import BaseAdapter, guarded_post_json
class GLMAdapter(BaseAdapter):
NAME = "glm"
DEFAULT_URL = "https://open.bigmodel.cn/api/paas/v4/chat/completions"
@property
def api_key(self) -> str:
return self.env.get("GLM_API_KEY", "")
@property
def model(self) -> str:
return self.env.get("GLM_MODEL", "glm-4-flash")
def chat(self, messages: list[dict]) -> str:
key = self.api_key
if not key:
raise RuntimeError("GLM_API_KEY is not set in the server environment (never in a file)")
payload = json.dumps({"model": self.model, "messages": messages}).encode("utf-8")
body = guarded_post_json(
self.env.get("GLM_BASE_URL", self.DEFAULT_URL),
headers={"Content-Type": "application/json",
"Authorization": f"Bearer {key}"},
payload=payload,
)
return body["choices"][0]["message"]["content"]