"""OpenCode Zen/Go model sync implementation. Fetches from OpenAI-compatible /v1/models endpoints (no auth needed for listing): Zen: https://opencode.ai/zen/v1/models (49 models, 6 free + big-pickle) Go: https://opencode.ai/zen/go/v1/models (20 models, subscription) Falls back to scraping docs pricing tables when API is unreachable: Zen: https://opencode.ai/docs/zen/#pricing Go: https://opencode.ai/docs/go/#usage-limits (pricing table is 2nd in section) Pricing is extracted from docs tables and stored in metadata under "pricing": {"input": 0.30, "output": 1.20, "cached_read": 0.06, "cached_write": null} """ from __future__ import annotations import os import re import urllib.error import urllib.request from html import unescape from provider_sync.base import BaseSync, ModelInfo _DOCS_UA = "doomalaysocreate/1.0" _ZEN_FREE_IDS = frozenset({"big-pickle"}) # Models ending with `-free` suffix that are still PAID in Zen (only free via Go). # Despite the suffix convention, these require Zen credits and are not zero-cost. _ZEN_PAID_SUFFIX_EXCEPTIONS = frozenset({ "minimax-m3-free", "qwen3.6-plus-free", }) _ZEN_DOCS_API_URL = "https://opencode.ai/zen/v1/models" _ZEN_DOCS_DOCS_URL = "https://opencode.ai/docs/zen/#pricing" _GO_API_URL = "https://opencode.ai/zen/go/v1/models" _GO_DOCS_URL = "https://opencode.ai/docs/go/#usage-limits" class OpenCodeSync(BaseSync): requires_auth = False env_var = None def __init__(self, api_key: str | None = None, **kwargs) -> None: super().__init__(api_key, **kwargs) self.variant = kwargs.get("variant", "zen") if self.variant == "go": self.provider_name = "opencode-go" self.models_url = _GO_API_URL self.docs_url = _GO_DOCS_URL self.docs_section = "usage-limits" self.env_var = "OPENCODE_GO_API_KEY" else: self.provider_name = "opencode-zen" self.models_url = _ZEN_DOCS_API_URL self.docs_url = _ZEN_DOCS_DOCS_URL self.docs_section = "pricing" self.env_var = "OPENCODE_ZEN_API_KEY" if self.api_key is None: self.api_key = os.environ.get(self.env_var, "") def fetch_models(self) -> list[ModelInfo]: models = self._try_api() if models: return models return self._try_docs_fallback() def _try_api(self) -> list[ModelInfo]: headers = self._build_auth_header() if self.api_key else None data = self._make_request(self.models_url, headers) if not data: return [] models: list[ModelInfo] = [] for item in data.get("data", []): if not isinstance(item, dict): continue model_id = item.get("id", "") if not model_id: continue normalized = self.normalize_model_id(model_id) models.append(ModelInfo( id=model_id, normalized_id=normalized, is_free=self._is_free(model_id), context_length=None, metadata=item, )) return models def _is_free(self, model_id: str) -> bool: if model_id in _ZEN_FREE_IDS: return True if model_id.endswith("-free") and model_id not in _ZEN_PAID_SUFFIX_EXCEPTIONS: return True return False def _try_docs_fallback(self) -> list[ModelInfo]: html = self._fetch_docs_page() if not html: return [] pricing_table = self._find_pricing_table(html) if not pricing_table: return [] return self._parse_pricing_table(pricing_table) def _fetch_docs_page(self) -> str: req = urllib.request.Request( self.docs_url, headers={"User-Agent": _DOCS_UA, "Accept": "text/html"}, ) try: with urllib.request.urlopen(req, timeout=15) as resp: return resp.read().decode("utf-8", errors="replace") except Exception: return "" @staticmethod def _find_pricing_table(html: str) -> list[list[str]] | None: """Find the first table with Input/Output pricing column headers. Returns list of rows, each row being a list of raw cell HTML strings. Skips the header row. Returns None if no pricing table found. """ for table_match in re.finditer(r"