""" 询价当前书签短协调:HTTP 收字、Worker 识图后补问必须读同一份。 不当六态、不当 checkpoint。只存当前 sender 的补问/核对草稿。 禁止:写入现网 Redis 前缀;把已出号工单只放这里当主账。 """ from __future__ import annotations import json import logging from typing import Any, Optional, Protocol from agent.redis_coord.keys import FLOW_BOOKMARK from agent.redis_coord.runtime import get_redis_runtime logger = logging.getLogger(__name__) _TTL_SEC = 24 * 3600 class BookmarkStore(Protocol): """单测可注入;生产走 Redis。""" def save(self, kind: str, sender_id: str, payload: dict[str, Any]) -> None: ... def load(self, kind: str, sender_id: str) -> Optional[dict[str, Any]]: ... def delete(self, kind: str, sender_id: str) -> None: ... class DictBookmarkStore: """两份 Flow 实例共用同一字典,模拟 HTTP/Worker。""" def __init__(self) -> None: self._d: dict[str, dict[str, Any]] = {} def save(self, kind: str, sender_id: str, payload: dict[str, Any]) -> None: self._d[f"{kind}:{sender_id}"] = json.loads(json.dumps(payload)) def load(self, kind: str, sender_id: str) -> Optional[dict[str, Any]]: row = self._d.get(f"{kind}:{sender_id}") return json.loads(json.dumps(row)) if row is not None else None def delete(self, kind: str, sender_id: str) -> None: self._d.pop(f"{kind}:{sender_id}", None) def _redis_ok() -> bool: try: return get_redis_runtime().client.backend == "redis" except Exception: return False def save_flow_bookmark( kind: str, sender_id: str, payload: dict[str, Any], *, store: Optional[BookmarkStore] = None, ) -> None: """写下当前书签。store 优先;否则仅真 Redis。""" sid = (sender_id or "").strip() name = (kind or "").strip() or "air" if not sid or not isinstance(payload, dict): return if store is not None: store.save(name, sid, payload) return if not _redis_ok(): return try: client = get_redis_runtime().client client.raw.set( client.key(FLOW_BOOKMARK, name, sid), json.dumps(payload, ensure_ascii=False), ex=_TTL_SEC, ) except Exception: logger.exception("写询价书签失败 kind=%s sender=%s", name, sid) def load_flow_bookmark( kind: str, sender_id: str, *, store: Optional[BookmarkStore] = None, ) -> dict[str, Any]: """取出当前书签。没有则空。""" sid = (sender_id or "").strip() name = (kind or "").strip() or "air" if not sid: return {} if store is not None: return dict(store.load(name, sid) or {}) if not _redis_ok(): return {} try: client = get_redis_runtime().client raw = client.raw.get(client.key(FLOW_BOOKMARK, name, sid)) if not raw: return {} if isinstance(raw, bytes): raw = raw.decode("utf-8") data = json.loads(raw) return dict(data) if isinstance(data, dict) else {} except Exception: logger.exception("读书签失败 kind=%s sender=%s", name, sid) return {} def delete_flow_bookmark( kind: str, sender_id: str, *, store: Optional[BookmarkStore] = None, ) -> None: """发图新开或结束当前草稿时删掉。已出号会话不靠这把键。""" sid = (sender_id or "").strip() name = (kind or "").strip() or "air" if not sid: return if store is not None: store.delete(name, sid) return if not _redis_ok(): return try: client = get_redis_runtime().client client.raw.delete(client.key(FLOW_BOOKMARK, name, sid)) except Exception: logger.exception("删书签失败 kind=%s sender=%s", name, sid)