auto-save 2026-05-11 19:15 (~5)

This commit is contained in:
2026-05-11 19:15:34 +08:00
parent 77d800dcac
commit c78f112685
5 changed files with 324 additions and 10 deletions

View File

@@ -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: