| 1 | """WebUI session key helpers for the local browser gateway.""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | WEBUI_CHANNEL = "websocket" |
| 6 | _PREFIX = f"{WEBUI_CHANNEL}:" |
| 7 | |
| 8 | |
| 9 | class WebuiSessionKeyError(ValueError): |
| 10 | """Raised when a WebUI session key does not meet the persisted key shape.""" |
| 11 | |
| 12 | |
| 13 | def webui_session_key(user_id: str | None, chat_id: str) -> str: |
| 14 | """Build a browser session key (normally ``websocket:local:<chat>``).""" |
| 15 | if not user_id or not str(user_id).strip(): |
| 16 | raise WebuiSessionKeyError("user_id is required for WebUI session keys") |
| 17 | if not chat_id or not str(chat_id).strip(): |
| 18 | raise WebuiSessionKeyError("chat_id is required for WebUI session keys") |
| 19 | return f"{WEBUI_CHANNEL}:{user_id}:{chat_id}" |
| 20 | |
| 21 | |
| 22 | def is_legacy_two_part_webui_session_key(key: str) -> bool: |
| 23 | """Return True for deprecated ``websocket:<chat_id>`` keys (no user segment).""" |
| 24 | if not key.startswith(_PREFIX): |
| 25 | return False |
| 26 | rest = key[len(_PREFIX) :] |
| 27 | return bool(rest) and ":" not in rest |
| 28 | |
| 29 | |
| 30 | def parse_webui_session_key(key: str) -> tuple[str, str] | None: |
| 31 | """Return ``(user_id, chat_id)`` for valid three-part keys; None otherwise.""" |
| 32 | if is_legacy_two_part_webui_session_key(key): |
| 33 | return None |
| 34 | if not key.startswith(_PREFIX): |
| 35 | return None |
| 36 | rest = key[len(_PREFIX) :] |
| 37 | if not rest or ":" not in rest: |
| 38 | return None |
| 39 | user_id, _, chat_id = rest.partition(":") |
| 40 | if not user_id or not chat_id: |
| 41 | return None |
| 42 | return user_id, chat_id |
| 43 | |
| 44 | |
| 45 | def webui_wire_chat_id(session_key: str) -> str: |
| 46 | """Extract the wire ``chat_id`` (uuid) from a persisted session key.""" |
| 47 | parsed = parse_webui_session_key(session_key) |
| 48 | return parsed[1] if parsed else "" |
| 49 | |
| 50 | |
| 51 | def session_workspace_dir_name(session_key: str) -> str: |
| 52 | """Map a persisted session key to a workspace subdirectory name. |
| 53 | |
| 54 | ``websocket:local:<chatId>`` becomes ``websocket_local_<chatId>``, |
| 55 | matching how WebUI sessions are laid out on disk. |
| 56 | """ |
| 57 | return session_key.replace(":", "_").replace("/", "_") |
| 58 |