{"repo": "ambientcrypto/agsm", "files": {"grok_client.py": {"13fc8ba476f2b35951bc0f5219bfca8514760dce": {"t": 1785674555.6111212, "n": 3865, "h": "ed7f5ad934d4d97d", "c": "from __future__ import annotations\n\nfrom typing import Optional, Generator, Any\nfrom pathlib import Path\nimport os\n\n\nclass GrokClient:\n \"\"\"\n Minimal xAI Grok client with SDK-or-HTTP fallback.\n\n - Prefers `xai-sdk` if installed; otherwise uses the HTTP OpenAI-compatible endpoint.\n - Streaming is not currently implemented; `stream_complete()` yields the non-stream result once.\n \"\"\"\n\n def __init__(self, api_key: Optional[str] = None, model: str = \"grok-4\", timeout: int = 180):\n self.api_key = api_key or self._load_key()\n self.model = model or \"grok-4\"\n self.timeout = int(timeout)\n self._sdk = None # lazily created xai-sdk client\n self.last_usage: Optional[dict] = None\n self.last_error: Optional[str] = None\n\n def _load_key(self) -> str:\n k = os.getenv(\"XAI_API_KEY\")\n if k and k.strip():\n return k.strip()\n # local files for convenience\n for cand in [Path.cwd() / \"xai_api_key.txt\", Path.home() / \".xai_api_key\"]:\n if cand.exists():\n t = cand.read_text(encoding=\"utf-8\").strip()\n if t:\n return t\n raise RuntimeError(\"XAI_API_KEY not found. Create xai_api_key.txt or set env var.\")\n\n def _ensure_sdk(self) -> Optional[Any]:\n if self._sdk is not None:\n return self._sdk\n try:\n from xai_sdk import Client as XAIClient # type: ignore\n self._sdk = XAIClient(api_key=self.api_key)\n return self._sdk\n except Exception:\n self._sdk = None\n return None\n\n def complete(self, prompt: str, max_tokens: int = 600, temperature: float = 0.6, stream: bool = False) -> str:\n if stream:\n return \"\".join(self.stream_complete(prompt, max_tokens=max_tokens, temperature=temperature))\n self.last_error = None\n # Try SDK first\n sdk = self._ensure_sdk()\n if sdk is not None:\n try:\n from xai_sdk.chat import user as xai_user # type: ignore\n\n chat = sdk.chat.create(model=self.model)\n chat.append(xai_user(prompt))\n resp = chat.sample()\n text = getattr(resp, \"content\", None)\n if isinstance(text, str) and text.strip():\n return text.strip()\n return str(resp)\n except Exception as e:\n self.last_error = str(e)\n # fall through to HTTP\n\n # HTTP fallback: OpenAI-compatible Chat Completions\n try:\n import requests\n headers = {\n \"Authorization\": f\"Bearer {self.api_key}\",\n \"Content-Type\": \"application/json\",\n }\n body = {\n \"model\": self.model,\n \"messages\": [{\"role\": \"user\", \"content\": prompt}],\n \"max_tokens\": int(max_tokens),\n \"temperature\": float(temperature),\n \"stream\": False,\n }\n resp = requests.post(\"https://api.x.ai/v1/chat/completions\", headers=headers, json=body, timeout=self.timeout)\n if resp.status_code != 200:\n self.last_error = resp.text or \"\"\n return \"\"\n data = resp.json()\n try:\n return (data[\"choices\"][0][\"message\"][\"content\"] or \"\").strip()\n except Exception:\n return \"\"\n except Exception as e:\n self.last_error = str(e)\n return \"\"\n\n def stream_complete(self, prompt: str, max_tokens: int = 600, temperature: float = 0.6) -> Generator[str, None, None]:\n # No streaming support yet; yield once using non-stream result\n out = self.complete(prompt, max_tokens=max_tokens, temperature=temperature, stream=False)\n if out:\n yield out\n\n"}}}}