| 1 | from __future__ import annotations |
| 2 | |
| 3 | from typing import Any |
| 4 | |
| 5 | from helpers import plugins |
| 6 | |
| 7 | |
| 8 | PLUGIN_NAME = "_oauth" |
| 9 | |
| 10 | DEFAULT_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann" |
| 11 | DEFAULT_CODEX_ISSUER = "https://auth.openai.com" |
| 12 | DEFAULT_CODEX_TOKEN_URL = "https://auth.openai.com/oauth/token" |
| 13 | DEFAULT_CODEX_BASE_URL = "https://chatgpt.com/backend-api/codex" |
| 14 | DEFAULT_CODEX_SCOPES = [ |
| 15 | "openid", |
| 16 | "profile", |
| 17 | "email", |
| 18 | "offline_access", |
| 19 | "api.connectors.read", |
| 20 | "api.connectors.invoke", |
| 21 | ] |
| 22 | CODEX_REASONING_EFFORTS = {"default", "minimal", "low", "medium", "high", "xhigh"} |
| 23 | CODEX_REASONING_SUMMARIES = {"off", "auto", "concise", "detailed"} |
| 24 | CODEX_TEXT_VERBOSITIES = {"default", "low", "medium", "high"} |
| 25 | DEFAULT_GEMINI_API_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai" |
| 26 | DEFAULT_GEMINI_API_SCOPES = [ |
| 27 | "openid", |
| 28 | "email", |
| 29 | "profile", |
| 30 | "https://www.googleapis.com/auth/cloud-platform", |
| 31 | "https://www.googleapis.com/auth/generative-language.retriever", |
| 32 | ] |
| 33 | |
| 34 | |
| 35 | def oauth_config() -> dict[str, Any]: |
| 36 | value = plugins.get_plugin_config(PLUGIN_NAME) or {} |
| 37 | return value if isinstance(value, dict) else {} |
| 38 | |
| 39 | |
| 40 | def codex_config(config: dict[str, Any] | None = None) -> dict[str, Any]: |
| 41 | source = config if isinstance(config, dict) else oauth_config() |
| 42 | raw = source.get("codex", {}) if isinstance(source, dict) else {} |
| 43 | raw = raw if isinstance(raw, dict) else {} |
| 44 | |
| 45 | return { |
| 46 | "enabled": _as_bool(raw.get("enabled"), True), |
| 47 | "auth_file_path": _as_str(raw.get("auth_file_path")), |
| 48 | "issuer": _trim_url(raw.get("issuer"), DEFAULT_CODEX_ISSUER), |
| 49 | "token_url": _as_str(raw.get("token_url")) or DEFAULT_CODEX_TOKEN_URL, |
| 50 | "client_id": _as_str(raw.get("client_id")) or DEFAULT_CODEX_CLIENT_ID, |
| 51 | "scopes": _as_str_list(raw.get("scopes")) or DEFAULT_CODEX_SCOPES, |
| 52 | "open_browser_from_server": _as_bool(raw.get("open_browser_from_server"), False), |
| 53 | "forced_workspace_id": _as_str(raw.get("forced_workspace_id")), |
| 54 | "upstream_base_url": _trim_url(raw.get("upstream_base_url"), DEFAULT_CODEX_BASE_URL), |
| 55 | "codex_version": _as_str(raw.get("codex_version")), |
| 56 | "models": _as_str_list(raw.get("models")), |
| 57 | "request_timeout_seconds": _as_int(raw.get("request_timeout_seconds"), 120), |
| 58 | "reasoning_effort": _as_choice( |
| 59 | raw.get("reasoning_effort"), CODEX_REASONING_EFFORTS, "high" |
| 60 | ), |
| 61 | "reasoning_summary": _as_choice( |
| 62 | raw.get("reasoning_summary"), CODEX_REASONING_SUMMARIES, "auto" |
| 63 | ), |
| 64 | "text_verbosity": _as_choice( |
| 65 | raw.get("text_verbosity"), CODEX_TEXT_VERBOSITIES, "medium" |
| 66 | ), |
| 67 | "proxy_base_path": _normalize_base_path(raw.get("proxy_base_path"), "/oauth/codex"), |
| 68 | "callback_path": _normalize_base_path(raw.get("callback_path"), "/auth/callback"), |
| 69 | "require_proxy_token": _as_bool(raw.get("require_proxy_token"), False), |
| 70 | "proxy_token": _as_str(raw.get("proxy_token")), |
| 71 | } |
| 72 | |
| 73 | |
| 74 | def gemini_api_config(config: dict[str, Any] | None = None) -> dict[str, Any]: |
| 75 | source = config if isinstance(config, dict) else oauth_config() |
| 76 | raw = source.get("gemini_api", {}) if isinstance(source, dict) else {} |
| 77 | raw = raw if isinstance(raw, dict) else {} |
| 78 | |
| 79 | return { |
| 80 | "enabled": _as_bool(raw.get("enabled"), True), |
| 81 | "client_id": _as_str(raw.get("client_id")), |
| 82 | "client_secret": _as_str(raw.get("client_secret")), |
| 83 | "scopes": _as_str_list(raw.get("scopes")) or DEFAULT_GEMINI_API_SCOPES, |
| 84 | "quota_project_id": _as_str(raw.get("quota_project_id")), |
| 85 | "api_base_url": _trim_url(raw.get("api_base_url"), DEFAULT_GEMINI_API_BASE_URL), |
| 86 | "proxy_base_path": _normalize_base_path(raw.get("proxy_base_path"), "/oauth/gemini-api"), |
| 87 | "callback_path": _normalize_base_path(raw.get("callback_path"), "/oauth/gemini-api/callback"), |
| 88 | } |
| 89 | |
| 90 | |
| 91 | def _as_str(value: Any) -> str: |
| 92 | if value is None: |
| 93 | return "" |
| 94 | return str(value).strip() |
| 95 | |
| 96 | |
| 97 | def _as_int(value: Any, default: int) -> int: |
| 98 | try: |
| 99 | return int(value) |
| 100 | except (TypeError, ValueError): |
| 101 | return default |
| 102 | |
| 103 | |
| 104 | def _as_bool(value: Any, default: bool) -> bool: |
| 105 | if value is None or value == "": |
| 106 | return default |
| 107 | if isinstance(value, bool): |
| 108 | return value |
| 109 | if isinstance(value, (int, float)): |
| 110 | return bool(value) |
| 111 | normalized = str(value).strip().lower() |
| 112 | if normalized in {"1", "true", "yes", "on", "enabled"}: |
| 113 | return True |
| 114 | if normalized in {"0", "false", "no", "off", "disabled"}: |
| 115 | return False |
| 116 | return default |
| 117 | |
| 118 | |
| 119 | def _as_choice(value: Any, choices: set[str], default: str) -> str: |
| 120 | normalized = _as_str(value).lower() |
| 121 | return normalized if normalized in choices else default |
| 122 | |
| 123 | |
| 124 | def _as_str_list(value: Any) -> list[str]: |
| 125 | if value is None: |
| 126 | return [] |
| 127 | if isinstance(value, str): |
| 128 | values = value.replace(",", "\n").splitlines() |
| 129 | elif isinstance(value, (list, tuple, set)): |
| 130 | values = list(value) |
| 131 | else: |
| 132 | values = [value] |
| 133 | |
| 134 | result: list[str] = [] |
| 135 | seen: set[str] = set() |
| 136 | for item in values: |
| 137 | text = _as_str(item) |
| 138 | if not text or text in seen: |
| 139 | continue |
| 140 | seen.add(text) |
| 141 | result.append(text) |
| 142 | return result |
| 143 | |
| 144 | |
| 145 | def _trim_url(value: Any, default: str) -> str: |
| 146 | text = _as_str(value) or default |
| 147 | return text.rstrip("/") |
| 148 | |
| 149 | |
| 150 | def _normalize_base_path(value: Any, default: str) -> str: |
| 151 | text = _as_str(value) or default |
| 152 | if not text.startswith("/"): |
| 153 | text = "/" + text |
| 154 | return text.rstrip("/") or default |