Files
chenlin f3a8a38acd
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
web-platform-ci / Standalone decision service (no cloud credentials) (pull_request) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (pull_request) Has been cancelled
web-platform-ci / Playwright E2E (pull_request) Has been cancelled
lekiwi-compatibility / cpu-compatibility (pull_request) Has been cancelled
feat: release v1.0.1 CADWorld 网站与 LeKiwi 智能抓放
集成同源 BYOK 会话隔离、精简模型设置、官方订阅入口和 HTTPS 发布运维;保留本地训练/调参与控制能力。同步 npm 版本及 CHANGELOG,记录公网真实 API 验收仍待用户凭据。
2026-09-24 09:57:41 +08:00

145 lines
5.4 KiB
Python

"""Non-secret metadata outside the repository; API credentials are session-memory only."""
import ipaddress
import json
import os
import re
from dataclasses import dataclass, field
from pathlib import Path
from urllib.parse import urlsplit
from .protocol import DecisionError, fields
def endpoint(value):
if not isinstance(value, str) or len(value) > 512 or any(c.isspace() for c in value):
raise DecisionError("invalid_endpoint")
try:
url = urlsplit(value)
port = url.port
host = url.hostname
local = host == "localhost" or ipaddress.ip_address(host).is_loopback
except ValueError:
local = False
try:
port, host = url.port, url.hostname
except (ValueError, UnboundLocalError) as exc:
raise DecisionError("invalid_endpoint") from exc
if (
not host
or url.username
or url.password
or url.query
or url.fragment
or "?" in value
or "#" in value
or "\\" in value
or (port is not None and not 1 <= port <= 65535)
or url.scheme not in ("https", "http")
or (url.scheme == "http" and not local)
):
raise DecisionError("https_or_loopback_required")
return value.rstrip("/")
@dataclass(frozen=True)
class Connection:
protocol: str
base_url: str
model: str
key: str = field(default="", repr=False)
def public(self):
return {
"protocol": self.protocol,
"baseUrl": self.base_url,
"model": self.model,
"hasKey": bool(self.key),
"keyStorage": "memory-only",
}
class Connections:
def __init__(self, directory: Path):
repo = Path(__file__).resolve().parents[1]
directory = directory.expanduser().resolve()
if directory.is_relative_to(repo):
raise DecisionError("state_directory_must_be_outside_repository")
self.path = directory / "connections.json"
self.values = {}
if self.path.is_symlink():
raise DecisionError("saved_metadata_symlink_forbidden")
if self.path.exists():
try:
data = json.loads(self.path.read_text())
for role, config in data.items():
self.values[role] = self.parse({"role": role, **config, "apiKey": ""})
except (OSError, ValueError, DecisionError, TypeError, AttributeError):
raise DecisionError("invalid_saved_metadata") from None
@staticmethod
def parse(data):
fields(data, ["role", "protocol", "baseUrl", "model", "apiKey"])
role, protocol = data["role"], data["protocol"]
allowed = {
"llm": ("responses", "chat-completions", "codex"),
"jev": ("typesafe", "openrouter-decisions"),
}
if not isinstance(role, str) or role not in allowed or protocol not in allowed[role]:
raise DecisionError("invalid_provider")
if not isinstance(data["model"], str) or not re.fullmatch(
r"[A-Za-z0-9_./:-]{1,128}", data["model"]
):
raise DecisionError("invalid_model")
key = data["apiKey"]
if not isinstance(key, str) or len(key) > 4096 or any(c.isspace() for c in key):
raise DecisionError("invalid_key")
if key and any(key in str(data[k]) for k in ("model", "baseUrl")):
raise DecisionError("credential_in_metadata")
if protocol == "codex":
if data["baseUrl"] != "" or key:
raise DecisionError("codex_does_not_accept_api_keys_or_urls")
return Connection(protocol, "", data["model"])
return Connection(protocol, endpoint(data["baseUrl"]), data["model"], key)
def set(self, data):
conn = self.parse(data)
# Every update supplies a key anew: never send an old host's credentials to a new host.
values = {**self.values, data["role"]: conn}
metadata = {
role: {"protocol": c.protocol, "baseUrl": c.base_url, "model": c.model}
for role, c in values.items()
}
encoded = json.dumps(metadata)
if any(c.key and c.key in encoded for c in [*self.values.values(), *values.values()]):
raise DecisionError("credential_in_metadata")
self.path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
temporary = self.path.with_suffix(".tmp")
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC | os.O_NOFOLLOW
fd = os.open(temporary, flags, 0o600)
with os.fdopen(fd, "w") as stream:
os.fchmod(stream.fileno(), 0o600)
json.dump(metadata, stream)
os.replace(temporary, self.path)
self.values = values
return conn.public()
def get(self, role):
if role not in self.values:
raise DecisionError("connection_not_configured", 409)
conn = self.values[role]
if conn.protocol != "codex" and not conn.key:
raise DecisionError("api_key_required", 409)
return conn
def redact(self, value):
if isinstance(value, str):
for conn in self.values.values():
if conn.key:
value = value.replace(conn.key, "[redacted]")
elif isinstance(value, dict):
return {key: self.redact(item) for key, item in value.items()}
elif isinstance(value, list):
return [self.redact(item) for item in value]
return value