auto-save 2026-05-11 17:22 (~2)
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user