Investment Plans workspace
Open raw ↗
"""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"]