고객사 실측: 게이트웨이 로그 parts=['text']. OpenCode 는 모델 설정에 이미지 입력 표시가 없으면 파트를 떨구고 텍스트만 보냄. render_opencode 가 AAF_FABRIX_VISION_MODEL_ID 모델에 attachment/modalities 를 붙이고, deploy.sh 가 OPENCODE_VISION_MODEL=gateway/605 를 채워 백엔드가 이미지 턴에 그 모델을 지정. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
83 lines
3.4 KiB
Python
83 lines
3.4 KiB
Python
# -*- 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")
|