"""사장님 에이전트 설정 — 루트 .env 하나만 읽는다(APP_ENV=test 면 .env 를 읽지 않는다). ★ SNS 게재(social_config)와 파일을 가른 이유는 도메인이 다르기 때문이다. SNS 게재는 **되돌릴 수 없는** 대외 발화이고, 에이전트는 사장님이 자기 사이트를 고치는 창구다. 승인 강도도 보관하는 것도 다르다 — 설정이 한 파일에 섞이면 "이 값이 무엇을 여는가" 가 흐려진다. """ from pydantic_settings import BaseSettings from config.config_models import _BASE class AgentConfig(BaseSettings): model_config = _BASE # 카카오톡 채널 공개 ID(`_xaBcD` 형태). 사장님이 채널을 찾아 코드를 입력해야 하므로 # ★ 이 값이 없으면 연결 화면 자체를 열지 않는다 — 어디에 코드를 칠지 말해 줄 수 # 없는데 코드만 발급하면, 사장님에게는 고장난 화면이다(Threads 카드와 같은 규칙). KAKAO_CHANNEL_PUBLIC_ID: str = "" # 코드 수명. 사장님이 화면을 보고 카톡을 열어 치는 동작이라 짧아도 된다. KAKAO_LINK_CODE_TTL_MIN: int = 10 # 코드가 짧아서(사람이 손으로 친다) 무차별 대입이 가능하다. 시도 수로 끊는다. KAKAO_LINK_MAX_ATTEMPTS: int = 5 # 빌더 화면의 대화창. 2026-09-21 에 한 번 닫았다가(카카오 채널 보류) 채널 인증이 # 끝나 다시 열었다(2026-09-22). # ★ 이 값이 "1" 이어도 **LLM 키가 없으면 안 열린다**(runtime.is_configured 가 둘 다 본다) — # 키 없는 환경에서 켜 둔 채 잊어도 "눌러도 안 되는 입구" 가 생기지 않는다. # 다시 닫을 일이 생기면 이 값만 "0" 으로 되돌린다. 코드를 되짚지 않는다. AGENT_CHAT_ENABLED: str = "1" # ★ 카카오 웹훅 인증. **오픈빌더는 서명을 주지 않는다** — URL 만 알면 누구나 이 엔드포인트를 # 때릴 수 있고, user.id 를 아무 값이나 넣으면 **그 사장님 행세를 한다.** 신원 연결 # (owner_kakao_links)이 통째로 무의미해진다. # 그래서 이 값이 없으면 **엔드포인트 자체를 띄우지 않는다**(404). 반쯤 열린 상태를 # 만들지 않는 것은 Threads 연결과 같은 규칙이다. # 만드는 법: python -c "import secrets; print(secrets.token_urlsafe(32))" KAKAO_WEBHOOK_SECRET: str = "" # 우리 봇이 맞는지 한 겹 더 본다. 시크릿이 아니라 오발송을 거르는 용도라 비워도 된다. # ★ Event API(챗봇이 먼저 보내기)는 이 값이 **필수**다 — 요청 주소에 봇 ID 가 들어간다. KAKAO_BOT_ID: str = "" # ★ Event API — 연결된 사장님에게 챗봇이 먼저 말을 거는 통로(services/external/kakao_event.py). # **채널을 연결한 비즈니스 인증 앱**의 REST API 키. 비어 있으면 Event API 는 존재하지 않는다. # ★ 기존 KAKAO_REST_API_KEY(카카오 로컬 API, 주소 검색 어댑터)와 이름을 일부러 갈랐다 — # 앱이 다를 수 있고, 같은 이름이면 한쪽을 채울 때 다른 쪽이 조용히 켜지거나 틀린 키로 나간다. KAKAO_BOT_REST_API_KEY: str = "" # "1" 이면 봇 ID 뒤에 "!" 를 붙여 **개발 채널**로 보낸다(운영 채널과 요청 주소가 다르다). # ★ KAKAO_BOT_ID 자체를 고쳐 쓰지 않는다 — 웹훅이 그 값으로 요청의 bot.id 를 대조한다. KAKAO_EVENT_DEV: str = "0" # 미니블로그 승인 알림을 메일에 더해 카톡으로도 보낸다. ★ 기본 꺼짐 — 오픈빌더에 이벤트 # 블록(스킬 연결)과 배포가 끝나기 전에 켜면 사장님 카톡에 빈 말풍선이 간다. KAKAO_APPROVAL_PUSH_ENABLED: str = "0" # 오픈빌더에 정의한 이벤트 이름. 말풍선을 그리는 블록이 이 이름에 걸려 있다. KAKAO_APPROVAL_EVENT_NAME: str = "post_approval" def get(name, default=""): return getattr(AgentConfig(), name, default) or default def chat_enabled() -> bool: return get("AGENT_CHAT_ENABLED", "0") == "1" def webhook_secret() -> str: return get("KAKAO_WEBHOOK_SECRET") def approval_push_enabled() -> bool: return get("KAKAO_APPROVAL_PUSH_ENABLED", "0") == "1" def kakao_link_enabled() -> bool: return bool(get("KAKAO_CHANNEL_PUBLIC_ID")) def channel_url() -> str: """사장님이 눌러서 채널로 가는 주소. 공개 ID 가 없으면 빈 문자열이다.""" public_id = get("KAKAO_CHANNEL_PUBLIC_ID") return f"http://pf.kakao.com/{public_id}" if public_id else ""