Spaces:
Running
Running
Track sessions via X-Session-ID header for accurate admin dashboard
Browse filesClaude Code --session-id <uuid> sends X-Session-ID on every request.
Use this header as the session identifier instead of inferring from
client IP (gateway_<ip>), which is unreliable when multiple sessions
share an IP. Fall back to gateway IP for non-session-ID requests.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- api/services.py +14 -12
api/services.py
CHANGED
|
@@ -106,6 +106,18 @@ def _get_client_ip(request: Request) -> str | None:
|
|
| 106 |
return None # Direct connection
|
| 107 |
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
class ClaudeProxyService:
|
| 110 |
"""Coordinate request optimization, model routing, and providers."""
|
| 111 |
|
|
@@ -122,16 +134,6 @@ class ClaudeProxyService:
|
|
| 122 |
self._token_counter = token_counter
|
| 123 |
self._session_tracker = SessionTracker.get_instance()
|
| 124 |
|
| 125 |
-
def _get_session_id(self, request: Request, request_data: MessagesRequest) -> str:
|
| 126 |
-
"""Extract or generate a session ID for gateway clients only."""
|
| 127 |
-
# Check if request came through a gateway/proxy
|
| 128 |
-
ip = _get_client_ip(request)
|
| 129 |
-
if ip is None:
|
| 130 |
-
return "direct" # Don't track direct connections
|
| 131 |
-
|
| 132 |
-
# Use gateway client IP as session identifier
|
| 133 |
-
return f"gateway_{ip}"
|
| 134 |
-
|
| 135 |
def create_message(self, request: Request, request_data: MessagesRequest) -> object:
|
| 136 |
"""Create a message response or streaming response with optional failover."""
|
| 137 |
try:
|
|
@@ -210,7 +212,7 @@ class ClaudeProxyService:
|
|
| 210 |
thinking_enabled=resolved.thinking_enabled,
|
| 211 |
)
|
| 212 |
|
| 213 |
-
session_id =
|
| 214 |
self._session_tracker.track_request_sync(session_id, resolved.provider_id)
|
| 215 |
|
| 216 |
request_id = f"req_{uuid.uuid4().hex[:12]}"
|
|
@@ -274,7 +276,7 @@ class ClaudeProxyService:
|
|
| 274 |
thinking_enabled=resolved.thinking_enabled,
|
| 275 |
)
|
| 276 |
|
| 277 |
-
session_id =
|
| 278 |
self._session_tracker.track_request_sync(
|
| 279 |
session_id, resolved.provider_id
|
| 280 |
)
|
|
|
|
| 106 |
return None # Direct connection
|
| 107 |
|
| 108 |
|
| 109 |
+
def _get_session_id(request: Request) -> str:
|
| 110 |
+
"""Get session ID from X-Session-ID header or fall back to gateway IP.
|
| 111 |
+
|
| 112 |
+
Claude Code sends X-Session-ID when started with --session-id <uuid>.
|
| 113 |
+
"""
|
| 114 |
+
session = request.headers.get("X-Session-ID")
|
| 115 |
+
if session:
|
| 116 |
+
return session
|
| 117 |
+
ip = _get_client_ip(request)
|
| 118 |
+
return f"gateway_{ip}" if ip else "direct"
|
| 119 |
+
|
| 120 |
+
|
| 121 |
class ClaudeProxyService:
|
| 122 |
"""Coordinate request optimization, model routing, and providers."""
|
| 123 |
|
|
|
|
| 134 |
self._token_counter = token_counter
|
| 135 |
self._session_tracker = SessionTracker.get_instance()
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
def create_message(self, request: Request, request_data: MessagesRequest) -> object:
|
| 138 |
"""Create a message response or streaming response with optional failover."""
|
| 139 |
try:
|
|
|
|
| 212 |
thinking_enabled=resolved.thinking_enabled,
|
| 213 |
)
|
| 214 |
|
| 215 |
+
session_id = _get_session_id(request)
|
| 216 |
self._session_tracker.track_request_sync(session_id, resolved.provider_id)
|
| 217 |
|
| 218 |
request_id = f"req_{uuid.uuid4().hex[:12]}"
|
|
|
|
| 276 |
thinking_enabled=resolved.thinking_enabled,
|
| 277 |
)
|
| 278 |
|
| 279 |
+
session_id = _get_session_id(request)
|
| 280 |
self._session_tracker.track_request_sync(
|
| 281 |
session_id, resolved.provider_id
|
| 282 |
)
|