auto-save 2026-05-11 17:22 (~2)

This commit is contained in:
2026-05-11 17:22:16 +08:00
parent 5fb59ac53d
commit e870792f20
2 changed files with 111 additions and 7 deletions

View File

@@ -1,12 +1,5 @@
{ {
"entries": [ "entries": [
{
"files_changed": 1,
"hash": "d042b1f",
"message": "auto-save 2026-05-10 08:39 (~1)",
"ts": "2026-05-10T08:39:07+08:00",
"type": "commit"
},
{ {
"files_changed": 1, "files_changed": 1,
"hash": "d1741e4", "hash": "d1741e4",
@@ -3264,6 +3257,13 @@
"type": "session-heartbeat", "type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 master · 1 项未提交变更 · 最近提交auto-save 2026-05-11 17:10 (~3)", "message": "Codex 会话活跃 · 最近命令codex · 分支 master · 1 项未提交变更 · 最近提交auto-save 2026-05-11 17:10 (~3)",
"files_changed": 1 "files_changed": 1
},
{
"ts": "2026-05-11T17:16:35+08:00",
"type": "commit",
"message": "auto-save 2026-05-11 17:16 (~1)",
"hash": "5fb59ac",
"files_changed": 1
} }
] ]
} }

View File

@@ -282,6 +282,110 @@ def http_json(
return json.loads(raw) return json.loads(raw)
def openai_completion_url(base_url: str) -> str:
base = base_url.rstrip("/")
if base.endswith("/chat/completions"):
return base
return f"{base}/chat/completions"
def read_env_value(key: str) -> str:
key = key.strip()
if not key:
return ""
return os.environ.get(key, "") or parse_env_file(Config.env_file).get(key, "")
def resolve_profile_api_key(profile: dict[str, Any]) -> str:
ref = str(profile.get("apiKeyRef") or "").strip()
if not ref or ref in {"服务器环境变量", "server env", "server-env"}:
return Config.hermes_api_key
if ref.startswith("env:"):
ref = ref[4:].strip()
if re.match(r"^[A-Z][A-Z0-9_]{1,120}$", ref):
return read_env_value(ref)
return ""
def profile_for_chat(profile_id: str) -> dict[str, Any]:
runtime = read_hermes_runtime_config()
config = normalize_ui_config(read_ui_config_file(), runtime)
profiles = config.get("modelProfiles") if isinstance(config.get("modelProfiles"), list) else []
if profile_id:
for profile in profiles:
if profile.get("id") == profile_id:
return profile
raise ValueError(f"unknown modelProfileId: {profile_id}")
for profile in profiles:
if profile.get("isDefault"):
return profile
if profiles:
return profiles[0]
return runtime_model_profile(runtime)
def build_profile_chat_request(body: dict[str, Any]) -> tuple[urllib.request.Request | None, bool, int, dict[str, Any] | None]:
profile_id = str(body.get("modelProfileId") or body.get("model_profile_id") or "").strip()
profile = profile_for_chat(profile_id)
if profile.get("enabled") is False:
return None, False, 400, {"code": 400, "msg": "model profile is disabled"}
base_url = str(profile.get("baseUrl") or "").strip() or Config.hermes_api_base
if not base_url:
return None, False, 400, {"code": 400, "msg": "model profile baseUrl is required"}
model = str(profile.get("model") or body.get("model") or Config.hermes_model).strip()
if not model:
return None, False, 400, {"code": 400, "msg": "model is required"}
if not re.match(r"^https?://", base_url):
return None, False, 400, {"code": 400, "msg": "model profile baseUrl must start with http:// or https://"}
payload = dict(body)
payload.pop("modelProfileId", None)
payload.pop("model_profile_id", None)
payload["model"] = model
stream = bool(payload.get("stream"))
headers = {"Accept": "text/event-stream" if stream else "application/json"}
api_key = resolve_profile_api_key(profile)
api_key_ref = str(profile.get("apiKeyRef") or "").strip()
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
elif api_key_ref and api_key_ref not in {"服务器环境变量", "server env", "server-env"}:
return None, stream, 400, {"code": 400, "msg": f"API key env not found: {api_key_ref}"}
url = openai_completion_url(base_url)
raw_body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(
url,
data=raw_body,
method="POST",
headers={
"Content-Type": "application/json; charset=utf-8",
**headers,
},
)
return req, stream, 200, None
def proxy_chat_completion(body: dict[str, Any]) -> tuple[int, dict[str, Any] | bytes, dict[str, str]]:
req, stream, status, error = build_profile_chat_request(body)
if error is not None or req is None:
return status, error or {"code": status, "msg": "invalid chat request"}, {}
try:
with urllib.request.urlopen(req, timeout=Config.request_timeout) as resp:
content_type = resp.headers.get("Content-Type") or (
"text/event-stream" if stream else "application/json"
)
if stream:
return resp.status, resp.read(), {"Content-Type": content_type}
raw = resp.read()
except urllib.error.HTTPError as exc:
raw = exc.read()
return exc.code, raw or json.dumps({"code": exc.code, "msg": exc.reason}).encode("utf-8"), {
"Content-Type": exc.headers.get("Content-Type") or "application/json"
}
return 200, raw, {"Content-Type": "application/json"}
def json_text(value: Any) -> str: def json_text(value: Any) -> str:
if isinstance(value, str): if isinstance(value, str):
try: try: