# -*- coding: utf-8 -*- """고객사(FabriX)용 opencode.json 만들기 — 템플릿의 ${이름} 을 .env 값으로 채움. python opencode/render_opencode.py # ../.env 읽어서 opencode/opencode.json 덮어씀 python opencode/render_opencode.py --check # 쓰진 않고 결과만 출력 개발(OpenRouter)로 돌아가려면 `git checkout opencode/opencode.json`. ABAP_OPENCODE web/deploy/bare/render-opencode.py 와 같은 규칙: AAF_FABRIX_MODELS="605:Gemma4,339:GaussO Flash,581:GaussO Think" → 모델 목록. 키가 곧 x-llm-model-id. 비어 있으면 AAF_FABRIX_MODEL_ID 하나(이름 FabriX). 기본 모델은 목록의 첫 번째. """ from __future__ import annotations import json import os import re import sys from pathlib import Path HERE = Path(__file__).resolve().parent sys.path.insert(0, str(HERE.parent)) from apps.gateway.fabrix import parse_models # noqa: E402 def load_env(path: Path) -> dict[str, str]: """settings.py 와 같은 규칙의 아주 단순한 .env 파서. 이미 있는 환경변수가 우선.""" out: dict[str, str] = {} if path.exists(): for line in path.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line or line.startswith("#") or "=" not in line: continue k, v = line.split("=", 1) v = v.split(" #", 1)[0].strip().strip("\"'") # 줄 끝 주석은 두 칸 띄고 # out[k.strip()] = v out.update({k: v for k, v in os.environ.items() if k.startswith(("AAF_", "BACKEND_PORT", "MCP_", "OPENCODE_"))}) return out def render(env: dict[str, str]) -> str: models = parse_models(env.get("AAF_FABRIX_MODELS", "")) or { (env.get("AAF_FABRIX_MODEL_ID") or "default"): "FabriX" } values = dict(env) # vision 모델엔 이미지 입력 가능 표시 — 없으면 OpenCode 가 이미지 파트를 떨구고 텍스트만 보냄(고객사 실측: parts=['text']) vision = (env.get("AAF_FABRIX_VISION_MODEL_ID") or "").strip() def _model(mid: str, name: str) -> dict: m: dict = {"name": name} if mid == vision: m["attachment"] = True m["modalities"] = {"input": ["text", "image"], "output": ["text"]} return m values["AAF_FABRIX_MODELS_JSON"] = json.dumps({mid: _model(mid, name) for mid, name in models.items()}, ensure_ascii=False) values["AAF_FABRIX_DEFAULT_MODEL"] = next(iter(models)) values.setdefault("BACKEND_PORT", "8001") values.setdefault("MCP_PORT", "8888") # SAP MCP: MCP_API_KEY 가 있으면 켬. 없으면 끔(로컬 개발) values["SAP_MCP_ENABLED"] = "true" if values.get("MCP_API_KEY") else "false" values.setdefault("MCP_API_KEY", "") values["AAF_GATEWAY_KEY"] = values.get("AAF_GATEWAY_KEY") or "x" # SDK 가 빈 키를 거부해서 더미 missing: list[str] = [] def fill(m: re.Match) -> str: v = values.get(m.group(1), "") if not v: missing.append(m.group(1)) return v text = re.sub(r"\$\{(\w+)\}", fill, (HERE / "opencode.fabrix.json.tmpl").read_text(encoding="utf-8")) if missing: print("경고: 비어 있는 값 —", ", ".join(sorted(set(missing))), file=sys.stderr) return text if __name__ == "__main__": text = render(load_env(HERE.parent / ".env")) if "--check" in sys.argv: print(text) else: (HERE / "opencode.json").write_text(text, encoding="utf-8") print("wrote", HERE / "opencode.json")