auto-save 2026-05-11 19:15 (~5)
This commit is contained in:
@@ -228,6 +228,9 @@ def reload_config_from_env_file() -> None:
|
||||
Config.feishu_app_secret = _env("FEISHU_APP_SECRET")
|
||||
Config.feishu_apps = _load_feishu_apps()
|
||||
Config.default_feishu_app_id = _env("FEISHU_DEFAULT_APP_ID", Config.feishu_app_id)
|
||||
Config.hermes_api_base = _env("HERMES_API_BASE", Config.hermes_api_base).rstrip("/")
|
||||
Config.hermes_api_key = _env("HERMES_API_KEY")
|
||||
Config.hermes_model = _env("HERMES_MODEL", Config.hermes_model)
|
||||
|
||||
|
||||
def sync_tokens_file() -> None:
|
||||
@@ -1140,6 +1143,125 @@ def is_admin_request(headers: dict[str, str]) -> tuple[bool, int, str]:
|
||||
return True, 200, "ok"
|
||||
|
||||
|
||||
COMMON_PROVIDER_KEYS = [
|
||||
"HERMES_API_KEY",
|
||||
"OPENROUTER_API_KEY",
|
||||
"OPENAI_API_KEY",
|
||||
"ANTHROPIC_API_KEY",
|
||||
"GOOGLE_API_KEY",
|
||||
"GEMINI_API_KEY",
|
||||
"DEEPSEEK_API_KEY",
|
||||
"XAI_API_KEY",
|
||||
"GROQ_API_KEY",
|
||||
"DASHSCOPE_API_KEY",
|
||||
]
|
||||
|
||||
|
||||
def provider_env_key(raw: Any) -> str:
|
||||
key = str(raw or "").strip()
|
||||
if key.startswith("env:"):
|
||||
key = key[4:].strip()
|
||||
if not re.match(r"^[A-Z][A-Z0-9_]{1,120}$", key):
|
||||
return ""
|
||||
return key
|
||||
|
||||
|
||||
def validate_provider_env_key(raw: Any) -> str:
|
||||
key = provider_env_key(raw)
|
||||
if not key:
|
||||
raise ValueError("env key must look like OPENROUTER_API_KEY")
|
||||
blocked_prefixes = ("FEISHU_", "HERMES_ADMIN_", "FEISHU_BRIDGE_")
|
||||
blocked_keys = {"PORT", "PATH", "HOME", "SHELL", "PWD"}
|
||||
if key in blocked_keys or key.startswith(blocked_prefixes):
|
||||
raise ValueError("this env key is managed by deployment, not by provider settings")
|
||||
if not key.endswith("_API_KEY"):
|
||||
raise ValueError("provider env key must end with _API_KEY")
|
||||
return key
|
||||
|
||||
|
||||
def provider_key_profiles() -> dict[str, list[str]]:
|
||||
result: dict[str, list[str]] = {}
|
||||
try:
|
||||
config = read_ui_config_file()
|
||||
except Exception:
|
||||
logging.warning("provider key profile map unavailable:\n%s", traceback.format_exc())
|
||||
return result
|
||||
profiles = config.get("modelProfiles") if isinstance(config.get("modelProfiles"), list) else []
|
||||
for profile in profiles:
|
||||
if not isinstance(profile, dict):
|
||||
continue
|
||||
key = provider_env_key(profile.get("apiKeyRef"))
|
||||
if not key:
|
||||
continue
|
||||
name = str(profile.get("name") or profile.get("model") or "Profile")[:80]
|
||||
result.setdefault(key, []).append(name)
|
||||
return result
|
||||
|
||||
|
||||
def handle_provider_keys_get(headers: dict[str, str]) -> tuple[int, dict[str, Any]]:
|
||||
ok, status, message = is_admin_request(headers)
|
||||
if not ok:
|
||||
return status, {"code": status, "msg": message}
|
||||
try:
|
||||
env = parse_env_file(Config.env_file)
|
||||
profile_map = provider_key_profiles()
|
||||
keys = set(COMMON_PROVIDER_KEYS) | set(profile_map.keys())
|
||||
payload = []
|
||||
for key in sorted(keys):
|
||||
present = bool(os.environ.get(key) or env.get(key))
|
||||
payload.append({
|
||||
"key": key,
|
||||
"present": present,
|
||||
"profiles": profile_map.get(key, []),
|
||||
})
|
||||
return 200, {"code": 0, "msg": "ok", "keys": payload}
|
||||
except Exception as exc:
|
||||
logging.error("failed to read provider keys:\n%s", traceback.format_exc())
|
||||
return 500, {"code": 500, "msg": str(exc)}
|
||||
|
||||
|
||||
def handle_provider_keys_post(headers: dict[str, str], body: dict[str, Any]) -> tuple[int, dict[str, Any]]:
|
||||
ok, status, message = is_admin_request(headers)
|
||||
if not ok:
|
||||
return status, {"code": status, "msg": message}
|
||||
try:
|
||||
key = validate_provider_env_key(body.get("key"))
|
||||
value = str(body.get("value") or body.get("api_key") or "").strip()
|
||||
if not value:
|
||||
raise ValueError("api key value is required")
|
||||
if "\n" in value or "\r" in value:
|
||||
raise ValueError("api key value must be a single line")
|
||||
if len(value) > 10000:
|
||||
raise ValueError("api key value is too large")
|
||||
write_env_updates(Config.env_file, {key: value})
|
||||
reload_config_from_env_file()
|
||||
logging.info("updated provider env key %s", key)
|
||||
return 200, {"code": 0, "msg": "ok", "key": {"key": key, "present": True}}
|
||||
except ValueError as exc:
|
||||
return 400, {"code": 400, "msg": str(exc)}
|
||||
except Exception as exc:
|
||||
logging.error("failed to write provider key:\n%s", traceback.format_exc())
|
||||
return 500, {"code": 500, "msg": str(exc)}
|
||||
|
||||
|
||||
def handle_provider_keys_delete(path: str, headers: dict[str, str]) -> tuple[int, dict[str, Any]]:
|
||||
ok, status, message = is_admin_request(headers)
|
||||
if not ok:
|
||||
return status, {"code": status, "msg": message}
|
||||
try:
|
||||
raw_key = urllib.parse.unquote(path[len("/feishu/provider-keys/") :].strip("/"))
|
||||
key = validate_provider_env_key(raw_key)
|
||||
write_env_removals(Config.env_file, {key})
|
||||
reload_config_from_env_file()
|
||||
logging.info("removed provider env key %s", key)
|
||||
return 200, {"code": 0, "msg": "ok", "key": {"key": key, "present": False}}
|
||||
except ValueError as exc:
|
||||
return 400, {"code": 400, "msg": str(exc)}
|
||||
except Exception as exc:
|
||||
logging.error("failed to remove provider key:\n%s", traceback.format_exc())
|
||||
return 500, {"code": 500, "msg": str(exc)}
|
||||
|
||||
|
||||
def validate_feishu_credentials(app_id: str, app_secret: str) -> None:
|
||||
data = http_json(
|
||||
"POST",
|
||||
@@ -1348,6 +1470,10 @@ class Handler(BaseHTTPRequestHandler):
|
||||
status, payload = handle_ui_config_get(headers)
|
||||
self.send_json(status, payload)
|
||||
return
|
||||
if self.path == "/feishu/provider-keys":
|
||||
status, payload = handle_provider_keys_get(headers)
|
||||
self.send_json(status, payload)
|
||||
return
|
||||
self.send_json(404, {"code": 404, "msg": "not found"})
|
||||
|
||||
def do_POST(self) -> None:
|
||||
@@ -1362,6 +1488,8 @@ class Handler(BaseHTTPRequestHandler):
|
||||
status, payload = handle_hermes_config_post(headers, body)
|
||||
elif self.path == "/feishu/ui-config":
|
||||
status, payload = handle_ui_config_post(headers, body)
|
||||
elif self.path == "/feishu/provider-keys":
|
||||
status, payload = handle_provider_keys_post(headers, body)
|
||||
elif self.path == "/feishu/chat/completions":
|
||||
ok, status, message = is_admin_request(headers)
|
||||
if not ok:
|
||||
@@ -1383,6 +1511,8 @@ class Handler(BaseHTTPRequestHandler):
|
||||
headers = {key.lower(): value for key, value in self.headers.items()}
|
||||
if self.path.startswith("/feishu/apps/"):
|
||||
status, payload = handle_apps_delete(self.path, headers)
|
||||
elif self.path.startswith("/feishu/provider-keys/"):
|
||||
status, payload = handle_provider_keys_delete(self.path, headers)
|
||||
else:
|
||||
status, payload = 404, {"code": 404, "msg": "not found"}
|
||||
except Exception as exc:
|
||||
|
||||
Reference in New Issue
Block a user