auto-save 2026-05-11 17:22 (~2)
This commit is contained in:
@@ -1,12 +1,5 @@
|
||||
{
|
||||
"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,
|
||||
"hash": "d1741e4",
|
||||
@@ -3264,6 +3257,13 @@
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 分支 master · 1 项未提交变更 · 最近提交:auto-save 2026-05-11 17:10 (~3)",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -282,6 +282,110 @@ def http_json(
|
||||
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:
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user