From 120487d4a039e9b5449bf2e44fa05f8b7d9bdff0 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 24 Apr 2026 12:10:49 +0800 Subject: [PATCH] bugfi --- hermes_web_cli/init.py | 986 ++++++++++++++++++++--------------------- 1 file changed, 493 insertions(+), 493 deletions(-) diff --git a/hermes_web_cli/init.py b/hermes_web_cli/init.py index b293780..009901a 100644 --- a/hermes_web_cli/init.py +++ b/hermes_web_cli/init.py @@ -28,537 +28,537 @@ from .db_tables import TABLE_DEFINITIONS from .crud_ops import SERVICES_CRUD, SESSIONS_CRUD, SETTINGS_CRUD def load_hermes_web_cli(): - """Initialize and load the hermes-web-cli module. - - This function is called by Sage system during module loading. - It registers all module functions with the ServerEnv instance - so they can be called directly from .ui and .dspy files. - """ - from ahserver.serverenv import ServerEnv - - # Initialize database tables if needed - try: - from .init_db import init_database - import asyncio - # Run database initialization in a new event loop if needed - try: - asyncio.get_running_loop() - # If we're already in an async context, create a task - asyncio.create_task(init_database()) - except RuntimeError: - # No running loop, run synchronously - asyncio.run(init_database()) - except Exception as e: - print(f"Warning: Database initialization failed: {str(e)}") - # Continue loading even if DB init fails - functions will handle errors gracefully - - # Get the ServerEnv instance - env = ServerEnv() - - # Register all module functions with ServerEnv - env.get_setting = get_setting - env.save_setting = save_setting - env.get_all_services = get_all_services - env.create_service = create_service - env.delete_service = delete_service - env.get_service_by_id = get_service_by_id - env.test_service_connection = test_service_connection - env.create_session = create_session - env.send_message_to_service = send_message_to_service - env.get_session_messages = get_session_messages - env.get_active_sessions = get_active_sessions - env.get_recent_sessions = get_recent_sessions - env.get_session_by_id = get_session_by_id - env.validate_service_url = validate_service_url - env.generate_session_id = generate_session_id - - # Also register the user context helper if needed - - return True + """Initialize and load the hermes-web-cli module. + + This function is called by Sage system during module loading. + It registers all module functions with the ServerEnv instance + so they can be called directly from .ui and .dspy files. + """ + from ahserver.serverenv import ServerEnv + + # Initialize database tables if needed + try: + from .init_db import init_database + import asyncio + # Run database initialization in a new event loop if needed + try: + asyncio.get_running_loop() + # If we're already in an async context, create a task + asyncio.create_task(init_database()) + except RuntimeError: + # No running loop, run synchronously + asyncio.run(init_database()) + except Exception as e: + print(f"Warning: Database initialization failed: {str(e)}") + # Continue loading even if DB init fails - functions will handle errors gracefully + + # Get the ServerEnv instance + env = ServerEnv() + + # Register all module functions with ServerEnv + env.get_setting = get_setting + env.save_setting = save_setting + env.get_all_services = get_all_services + env.create_service = create_service + env.delete_service = delete_service + env.get_service_by_id = get_service_by_id + env.test_service_connection = test_service_connection + env.create_session = create_session + env.send_message_to_service = send_message_to_service + env.get_session_messages = get_session_messages + env.get_active_sessions = get_active_sessions + env.get_recent_sessions = get_recent_sessions + env.get_session_by_id = get_session_by_id + env.validate_service_url = validate_service_url + env.generate_session_id = generate_session_id + + # Also register the user context helper if needed + + return True # Database operations using sqlor-database-module async def get_all_services(userid: str) -> List[Dict]: - """Get all registered Hermes services for the current user from database.""" - try: - # Query services table with user_id filter using sqlor-database-module + """Get all registered Hermes services for the current user from database.""" + try: + # Query services table with user_id filter using sqlor-database-module env = ServerEnv() async with get_sor_context(env, 'hermes-web-cli') as sor: - sql_template = SERVICES_CRUD['operations']['read_all']['sql_template'] - recs = await sor.sqlExe(sql_template, {'user_id': user_id}) - - # Convert datetime objects to ISO format strings for JSON serialization - result = [] - for rec in recs: - service_dict = dict(rec) - if 'created_at' in service_dict and service_dict['created_at']: - service_dict['created_at'] = service_dict['created_at'].isoformat() - if 'updated_at' in service_dict and service_dict['updated_at']: - service_dict['updated_at'] = service_dict['updated_at'].isoformat() - result.append(service_dict) - - return result - - except Exception as e: - print(f"Error getting services: {str(e)}") - # Return empty list on error - return [] - except Exception as e: - print(f"Error getting services: {e}") - return [] + sql_template = SERVICES_CRUD['operations']['read_all']['sql_template'] + recs = await sor.sqlExe(sql_template, {'user_id': user_id}) + + # Convert datetime objects to ISO format strings for JSON serialization + result = [] + for rec in recs: + service_dict = dict(rec) + if 'created_at' in service_dict and service_dict['created_at']: + service_dict['created_at'] = service_dict['created_at'].isoformat() + if 'updated_at' in service_dict and service_dict['updated_at']: + service_dict['updated_at'] = service_dict['updated_at'].isoformat() + result.append(service_dict) + + return result + + except Exception as e: + print(f"Error getting services: {str(e)}") + # Return empty list on error + return [] + except Exception as e: + print(f"Error getting services: {e}") + return [] async def create_service(userid: str, name: str, url: str, description: str = "", apikey: str = "") -> str: - """Create a new Hermes service registration for the current user.""" - try: - # Validate service URL - if not await validate_service_url(url): - raise ValueError("Invalid service URL") - - service_id = getID() - - # Save to database using sqlor-database-module + """Create a new Hermes service registration for the current user.""" + try: + # Validate service URL + if not await validate_service_url(url): + raise ValueError("Invalid service URL") + + service_id = getID() + + # Save to database using sqlor-database-module env = ServerEnv() async with get_sor_context(env, 'hermes-web-cli') as sor: - sql_template = SERVICES_CRUD['operations']['create']['sql_template'] - await sor.sqlExe(sql_template, { - 'id': service_id, - 'user_id': user_id, - 'name': name, - 'service_url': url, - 'description': description, - 'status': 'active' - }) - - return service_id - - except Exception as e: - print(f"Error creating service: {str(e)}") - raise + sql_template = SERVICES_CRUD['operations']['create']['sql_template'] + await sor.sqlExe(sql_template, { + 'id': service_id, + 'user_id': user_id, + 'name': name, + 'service_url': url, + 'description': description, + 'status': 'active' + }) + + return service_id + + except Exception as e: + print(f"Error creating service: {str(e)}") + raise async def delete_service(userid: str, service_id: str) -> bool: - """Delete a Hermes service registration (only if owned by current user).""" - try: - # Verify service belongs to current user before deletion - service = await get_service_by_id(service_id) - if not service: - return False - - if service.get("user_id") != user_id: - print(f"Permission denied: Service {service_id} does not belong to user {user_id}") - return False - - # Delete from database using sqlor-database-module + """Delete a Hermes service registration (only if owned by current user).""" + try: + # Verify service belongs to current user before deletion + service = await get_service_by_id(service_id) + if not service: + return False + + if service.get("user_id") != user_id: + print(f"Permission denied: Service {service_id} does not belong to user {user_id}") + return False + + # Delete from database using sqlor-database-module env = ServerEnv() async with get_sor_context(env, 'hermes-web-cli') as sor: - sql_template = SERVICES_CRUD['operations']['delete']['sql_template'] - await sor.sqlExe(sql_template, { - 'service_id': service_id, - 'user_id': user_id - }) - - # Also delete associated sessions - async with db.sqlorContext('hermes-web-cli') as sor: - await sor.sqlExe(""" - DELETE FROM sessions - WHERE service_id = ${service_id}$ AND user_id = ${user_id}$ - """, { - 'service_id': service_id, - 'user_id': user_id - }) - - return True - except Exception as e: - print(f"Error deleting service: {str(e)}") - return False + sql_template = SERVICES_CRUD['operations']['delete']['sql_template'] + await sor.sqlExe(sql_template, { + 'service_id': service_id, + 'user_id': user_id + }) + + # Also delete associated sessions + async with db.sqlorContext('hermes-web-cli') as sor: + await sor.sqlExe(""" + DELETE FROM sessions + WHERE service_id = ${service_id}$ AND user_id = ${user_id}$ + """, { + 'service_id': service_id, + 'user_id': user_id + }) + + return True + except Exception as e: + print(f"Error deleting service: {str(e)}") + return False async def get_service_by_id(userid: str, service_id: str) -> Optional[Dict]: - """Get service configuration by ID (only if owned by current user).""" - try: - # Query database directly with user_id filter for security + """Get service configuration by ID (only if owned by current user).""" + try: + # Query database directly with user_id filter for security env = ServerEnv() async with get_sor_context(env, 'hermes-web-cli') as sor: - sql_template = SERVICES_CRUD['operations']['read_by_id']['sql_template'] - recs = await sor.sqlExe(sql_template, { - 'service_id': service_id, - 'user_id': user_id - }) - - if len(recs) > 0: - service_dict = dict(recs[0]) - if 'created_at' in service_dict and service_dict['created_at']: - service_dict['created_at'] = service_dict['created_at'].isoformat() - if 'updated_at' in service_dict and service_dict['updated_at']: - service_dict['updated_at'] = service_dict['updated_at'].isoformat() - return service_dict - - return None - - except Exception as e: - print(f"Error getting service: {str(e)}") - return None + sql_template = SERVICES_CRUD['operations']['read_by_id']['sql_template'] + recs = await sor.sqlExe(sql_template, { + 'service_id': service_id, + 'user_id': user_id + }) + + if len(recs) > 0: + service_dict = dict(recs[0]) + if 'created_at' in service_dict and service_dict['created_at']: + service_dict['created_at'] = service_dict['created_at'].isoformat() + if 'updated_at' in service_dict and service_dict['updated_at']: + service_dict['updated_at'] = service_dict['updated_at'].isoformat() + return service_dict + + return None + + except Exception as e: + print(f"Error getting service: {str(e)}") + return None # Service connection testing async def test_service_connection(service_id: str) -> Tuple[bool, str]: - """Test connection to a Hermes service endpoint. - - Args: - service_id: The ID of the service to test - - Returns: - Tuple[bool, str]: (is_connected, status_message) - """ - try: - # Get service configuration (verify it belongs to current user) - service = await get_service_by_id(service_id) - if not service: - return False, "Service not found or access denied" - - url = service["service_url"] - apikey = service.get("apikey", "") - - # Prepare headers - headers = {} - if apikey: - headers["Authorization"] = f"Bearer {apikey}" - - # Test the /health endpoint or similar - timeout = aiohttp.ClientTimeout(total=10) - async with aiohttp.ClientSession(timeout=timeout) as session: - async with session.get(f"{url.rstrip('/')}/health", headers=headers) as response: - if response.status == 200: - return True, "Connected" - else: - return False, f"HTTP {response.status}" - except asyncio.TimeoutError: - return False, "Connection timeout" - except aiohttp.ClientConnectorError: - return False, "Connection refused" - except Exception as e: - return False, f"Error: {str(e)}" + """Test connection to a Hermes service endpoint. + + Args: + service_id: The ID of the service to test + + Returns: + Tuple[bool, str]: (is_connected, status_message) + """ + try: + # Get service configuration (verify it belongs to current user) + service = await get_service_by_id(service_id) + if not service: + return False, "Service not found or access denied" + + url = service["service_url"] + apikey = service.get("apikey", "") + + # Prepare headers + headers = {} + if apikey: + headers["Authorization"] = f"Bearer {apikey}" + + # Test the /health endpoint or similar + timeout = aiohttp.ClientTimeout(total=10) + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.get(f"{url.rstrip('/')}/health", headers=headers) as response: + if response.status == 200: + return True, "Connected" + else: + return False, f"HTTP {response.status}" + except asyncio.TimeoutError: + return False, "Connection timeout" + except aiohttp.ClientConnectorError: + return False, "Connection refused" + except Exception as e: + return False, f"Error: {str(e)}" # Session management async def create_session(service_id: str, user_id: str, user_message: str = "") -> str: - """Create a new session with a Hermes service.""" - try: - # Get service configuration (verify it belongs to current user) - service = await get_service_by_id(service_id) - if not service: - raise ValueError(f"Service {service_id} not found or access denied") - - service_url = service["service_url"] - apikey = service.get("apikey", "") - - # Prepare headers - headers = { - "Content-Type": "application/json" - } - - # Add Authorization header if API key is provided - if apikey: - headers["Authorization"] = f"Bearer {apikey}" - - # Call remote service API to create session - timeout = aiohttp.ClientTimeout(total=30) - async with aiohttp.ClientSession(timeout=timeout) as session: - async with session.post( - f"{service_url.rstrip('/')}/api/v1/sessions", - json={ - "user_id": user_id, - "initial_message": user_message if user_message else None - }, - headers=headers - ) as response: - response.raise_for_status() - result = await response.json() - - # Get the session ID from the remote service - remote_session_id = result.get("session_id", "") - if not remote_session_id: - raise ValueError("Remote service did not return a session ID") - - # Create local session record in database + """Create a new session with a Hermes service.""" + try: + # Get service configuration (verify it belongs to current user) + service = await get_service_by_id(service_id) + if not service: + raise ValueError(f"Service {service_id} not found or access denied") + + service_url = service["service_url"] + apikey = service.get("apikey", "") + + # Prepare headers + headers = { + "Content-Type": "application/json" + } + + # Add Authorization header if API key is provided + if apikey: + headers["Authorization"] = f"Bearer {apikey}" + + # Call remote service API to create session + timeout = aiohttp.ClientTimeout(total=30) + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.post( + f"{service_url.rstrip('/')}/api/v1/sessions", + json={ + "user_id": user_id, + "initial_message": user_message if user_message else None + }, + headers=headers + ) as response: + response.raise_for_status() + result = await response.json() + + # Get the session ID from the remote service + remote_session_id = result.get("session_id", "") + if not remote_session_id: + raise ValueError("Remote service did not return a session ID") + + # Create local session record in database env = ServerEnv() async with get_sor_context(env, 'hermes-web-cli') as sor: - sql_template = SESSIONS_CRUD['operations']['create']['sql_template'] - await sor.sqlExe(sql_template, { - 'session_id': remote_session_id, - 'user_id': user_id, - 'service_id': service_id, - 'session_name': None, - 'service_name': service.get("name", "Unknown Service") - }) - - # Return the session ID from the remote service - return remote_session_id - - except Exception as e: - print(f"Error creating session: {str(e)}") - raise + sql_template = SESSIONS_CRUD['operations']['create']['sql_template'] + await sor.sqlExe(sql_template, { + 'session_id': remote_session_id, + 'user_id': user_id, + 'service_id': service_id, + 'session_name': None, + 'service_name': service.get("name", "Unknown Service") + }) + + # Return the session ID from the remote service + return remote_session_id + + except Exception as e: + print(f"Error creating session: {str(e)}") + raise async def send_message_to_service(userid: str, service_id: str, session_id: str, message: str) -> Dict: - """Send a message to a Hermes service and get response (only if session owned by current user).""" - try: - # Verify session belongs to current user before sending message - session = await get_session_by_id(session_id) - if not session: - raise ValueError(f"Session {session_id} not found or access denied for user {user_id}") - - service = await get_service_by_id(userid, service_id) - if not service: - raise ValueError(f"Service {service_id} not found or access denied for user {user_id}") - - service_url = service["service_url"] - apikey = service.get("apikey", "") - - # Prepare headers - headers = { - "Content-Type": "application/json" - } - - # Add Authorization header if API key is provided - if apikey: - headers["Authorization"] = f"Bearer {apikey}" - - # Call remote service API - timeout = aiohttp.ClientTimeout(total=30) - async with aiohttp.ClientSession(timeout=timeout) as session: - async with session.post( - f"{service_url.rstrip('/')}/api/chat", - json={ - "session_id": session_id, - "message": message - }, - headers=headers - ) as response: - response.raise_for_status() - return await response.json() - except Exception as e: - print(f"Error sending message: {e}") - raise + """Send a message to a Hermes service and get response (only if session owned by current user).""" + try: + # Verify session belongs to current user before sending message + session = await get_session_by_id(session_id) + if not session: + raise ValueError(f"Session {session_id} not found or access denied for user {user_id}") + + service = await get_service_by_id(userid, service_id) + if not service: + raise ValueError(f"Service {service_id} not found or access denied for user {user_id}") + + service_url = service["service_url"] + apikey = service.get("apikey", "") + + # Prepare headers + headers = { + "Content-Type": "application/json" + } + + # Add Authorization header if API key is provided + if apikey: + headers["Authorization"] = f"Bearer {apikey}" + + # Call remote service API + timeout = aiohttp.ClientTimeout(total=30) + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.post( + f"{service_url.rstrip('/')}/api/chat", + json={ + "session_id": session_id, + "message": message + }, + headers=headers + ) as response: + response.raise_for_status() + return await response.json() + except Exception as e: + print(f"Error sending message: {e}") + raise async def get_session_messages(userid: str, session_id: str) -> List[Dict]: - """Get all messages for a session (only if session owned by current user).""" - try: - # Verify session belongs to current user before getting messages - session = await get_session_by_id(session_id) - if not session: - print(f"Session {session_id} not found or access denied for user {user_id}") - return [] - - # Get the associated service - service = await get_service_by_id(userid, session['service_id']) - if not service: - print(f"Service for session {session_id} not found or access denied") - return [] - - service_url = service["service_url"] - apikey = service.get("apikey", "") - - # Prepare headers - headers = { - "Content-Type": "application/json" - } - - # Add Authorization header if API key is provided - if apikey: - headers["Authorization"] = f"Bearer {apikey}" - - # Call remote service API to get messages - timeout = aiohttp.ClientTimeout(total=30) - async with aiohttp.ClientSession(timeout=timeout) as session: - async with session.get( - f"{service_url.rstrip('/')}/api/v1/sessions/{session_id}/messages", - headers=headers - ) as response: - response.raise_for_status() - messages = await response.json() - - # Update session last_active timestamp and message count in local database + """Get all messages for a session (only if session owned by current user).""" + try: + # Verify session belongs to current user before getting messages + session = await get_session_by_id(session_id) + if not session: + print(f"Session {session_id} not found or access denied for user {user_id}") + return [] + + # Get the associated service + service = await get_service_by_id(userid, session['service_id']) + if not service: + print(f"Service for session {session_id} not found or access denied") + return [] + + service_url = service["service_url"] + apikey = service.get("apikey", "") + + # Prepare headers + headers = { + "Content-Type": "application/json" + } + + # Add Authorization header if API key is provided + if apikey: + headers["Authorization"] = f"Bearer {apikey}" + + # Call remote service API to get messages + timeout = aiohttp.ClientTimeout(total=30) + async with aiohttp.ClientSession(timeout=timeout) as session: + async with session.get( + f"{service_url.rstrip('/')}/api/v1/sessions/{session_id}/messages", + headers=headers + ) as response: + response.raise_for_status() + messages = await response.json() + + # Update session last_active timestamp and message count in local database env = ServerEnv() async with get_sor_context(env, 'hermes-web-cli') as sor: - await sor.sqlExe(""" - UPDATE sessions - SET last_active = CURRENT_TIMESTAMP, - message_count = ${message_count}$ - WHERE session_id = ${session_id}$ AND user_id = ${user_id}$ - """, { - 'session_id': session_id, - 'user_id': user_id, - 'message_count': len(messages) - }) - - return messages - - except Exception as e: - print(f"Error getting session messages: {str(e)}") - return [] + await sor.sqlExe(""" + UPDATE sessions + SET last_active = CURRENT_TIMESTAMP, + message_count = ${message_count}$ + WHERE session_id = ${session_id}$ AND user_id = ${user_id}$ + """, { + 'session_id': session_id, + 'user_id': user_id, + 'message_count': len(messages) + }) + + return messages + + except Exception as e: + print(f"Error getting session messages: {str(e)}") + return [] # Active sessions management async def get_active_sessions(userid: str) -> List[Dict]: - """Get all active sessions for the current user from database.""" - try: - # Query the sessions table for active sessions belonging to current user using sqlor-database-module + """Get all active sessions for the current user from database.""" + try: + # Query the sessions table for active sessions belonging to current user using sqlor-database-module env = ServerEnv() async with get_sor_context(env, 'hermes-web-cli') as sor: - sql_template = SESSIONS_CRUD['operations']['read_active']['sql_template'] - recs = await sor.sqlExe(sql_template, {'user_id': user_id}) - - # Convert datetime objects to ISO format strings for JSON serialization - result = [] - for rec in recs: - session_dict = dict(rec) - if 'created_at' in session_dict and session_dict['created_at']: - session_dict['created_at'] = session_dict['created_at'].isoformat() - if 'last_active' in session_dict and session_dict['last_active']: - session_dict['last_active'] = session_dict['last_active'].isoformat() - result.append(session_dict) - - return result - - except Exception as e: - print(f"Error getting active sessions: {str(e)}") - return [] + sql_template = SESSIONS_CRUD['operations']['read_active']['sql_template'] + recs = await sor.sqlExe(sql_template, {'user_id': user_id}) + + # Convert datetime objects to ISO format strings for JSON serialization + result = [] + for rec in recs: + session_dict = dict(rec) + if 'created_at' in session_dict and session_dict['created_at']: + session_dict['created_at'] = session_dict['created_at'].isoformat() + if 'last_active' in session_dict and session_dict['last_active']: + session_dict['last_active'] = session_dict['last_active'].isoformat() + result.append(session_dict) + + return result + + except Exception as e: + print(f"Error getting active sessions: {str(e)}") + return [] async def get_recent_sessions(userid: str, limit: int = 5) -> List[Dict]: - """Get recent sessions for the current user from database, ordered by creation time (most recent first).""" - try: - # Query the sessions table for recent sessions belonging to current user using sqlor-database-module + """Get recent sessions for the current user from database, ordered by creation time (most recent first).""" + try: + # Query the sessions table for recent sessions belonging to current user using sqlor-database-module env = ServerEnv() async with get_sor_context(env, 'hermes-web-cli') as sor: - sql_template = SESSIONS_CRUD['operations']['read_recent']['sql_template'] - recs = await sor.sqlExe(sql_template, {'user_id': user_id, 'limit': limit}) - - # Convert datetime objects to ISO format strings for JSON serialization - result = [] - for rec in recs: - session_dict = dict(rec) - if 'created_at' in session_dict and session_dict['created_at']: - session_dict['created_at'] = session_dict['created_at'].isoformat() - if 'last_active' in session_dict and session_dict['last_active']: - session_dict['last_active'] = session_dict['last_active'].isoformat() - result.append(session_dict) - - return result - - except Exception as e: - print(f"Error getting recent sessions: {str(e)}") - return [] + sql_template = SESSIONS_CRUD['operations']['read_recent']['sql_template'] + recs = await sor.sqlExe(sql_template, {'user_id': user_id, 'limit': limit}) + + # Convert datetime objects to ISO format strings for JSON serialization + result = [] + for rec in recs: + session_dict = dict(rec) + if 'created_at' in session_dict and session_dict['created_at']: + session_dict['created_at'] = session_dict['created_at'].isoformat() + if 'last_active' in session_dict and session_dict['last_active']: + session_dict['last_active'] = session_dict['last_active'].isoformat() + result.append(session_dict) + + return result + + except Exception as e: + print(f"Error getting recent sessions: {str(e)}") + return [] async def get_session_by_id(userid, session_id: str) -> Optional[Dict]: - """Get session details by session ID (only if owned by current user).""" - try: - # Query database directly with user_id filter for security + """Get session details by session ID (only if owned by current user).""" + try: + # Query database directly with user_id filter for security env = ServerEnv() async with get_sor_context(env, 'hermes-web-cli') as sor: - sql_template = SESSIONS_CRUD['operations']['read_by_id']['sql_template'] - recs = await sor.sqlExe(sql_template, { - 'session_id': session_id, - 'user_id': user_id - }) - - if len(recs) > 0: - session_dict = dict(recs[0]) - if 'created_at' in session_dict and session_dict['created_at']: - session_dict['created_at'] = session_dict['created_at'].isoformat() - if 'last_active' in session_dict and session_dict['last_active']: - session_dict['last_active'] = session_dict['last_active'].isoformat() - return session_dict - - return None - - except Exception as e: - print(f"Error getting session by ID: {str(e)}") - return None + sql_template = SESSIONS_CRUD['operations']['read_by_id']['sql_template'] + recs = await sor.sqlExe(sql_template, { + 'session_id': session_id, + 'user_id': user_id + }) + + if len(recs) > 0: + session_dict = dict(recs[0]) + if 'created_at' in session_dict and session_dict['created_at']: + session_dict['created_at'] = session_dict['created_at'].isoformat() + if 'last_active' in session_dict and session_dict['last_active']: + session_dict['last_active'] = session_dict['last_active'].isoformat() + return session_dict + + return None + + except Exception as e: + print(f"Error getting session by ID: {str(e)}") + return None # Utility functions for validation def validate_service_url(url: str) -> bool: - """Validate if a URL is a valid Hermes service endpoint.""" - if not url.startswith(('http://', 'https://')): - return False - - # Additional validation can be added here - return True + """Validate if a URL is a valid Hermes service endpoint.""" + if not url.startswith(('http://', 'https://')): + return False + + # Additional validation can be added here + return True def generate_session_id() -> str: - """Generate a unique session ID.""" - return getID() + """Generate a unique session ID.""" + return getID() # Settings management async def get_setting() -> Dict: - """Get current user settings from database or return defaults.""" - import json - - # Get current user ID - user_id = await get_current_user_id() - - default_settings = { - "security": { - "require_auth": False, - "encrypt_storage": False - }, - "general": { - "default_model": "", - "session_timeout": 30, - "auto_save": True - }, - "appearance": { - "theme": "dark" - } - } - - try: - # Query user settings from database + """Get current user settings from database or return defaults.""" + import json + + # Get current user ID + user_id = await get_current_user_id() + + default_settings = { + "security": { + "require_auth": False, + "encrypt_storage": False + }, + "general": { + "default_model": "", + "session_timeout": 30, + "auto_save": True + }, + "appearance": { + "theme": "dark" + } + } + + try: + # Query user settings from database env = ServerEnv() async with get_sor_context(env, 'hermes-web-cli') as sor: - sql_template = SETTINGS_CRUD['operations']['read']['sql_template'] - recs = await sor.sqlExe(sql_template, {'user_id': user_id}) - - if len(recs) > 0: - settings_json = recs[0]['settings_json'] - if settings_json: - saved_settings = json.loads(settings_json) - # Merge with defaults to ensure all keys exist - for section, defaults in default_settings.items(): - if section not in saved_settings: - saved_settings[section] = defaults - else: - for key, value in defaults.items(): - if key not in saved_settings[section]: - saved_settings[section][key] = value - return saved_settings - - except Exception as e: - print(f"Error getting settings: {str(e)}") - # Fall back to defaults on error - - return default_settings + sql_template = SETTINGS_CRUD['operations']['read']['sql_template'] + recs = await sor.sqlExe(sql_template, {'user_id': user_id}) + + if len(recs) > 0: + settings_json = recs[0]['settings_json'] + if settings_json: + saved_settings = json.loads(settings_json) + # Merge with defaults to ensure all keys exist + for section, defaults in default_settings.items(): + if section not in saved_settings: + saved_settings[section] = defaults + else: + for key, value in defaults.items(): + if key not in saved_settings[section]: + saved_settings[section][key] = value + return saved_settings + + except Exception as e: + print(f"Error getting settings: {str(e)}") + # Fall back to defaults on error + + return default_settings async def save_setting(section: str, key: str, value) -> bool: - """Save a specific setting value to current user's database record.""" - import json - - # Get current user ID - user_id = await get_current_user_id() - - # Load existing settings or start with defaults - settings = await get_setting() - - # Update the specific setting - if section not in settings: - settings[section] = {} - settings[section][key] = value - - try: - # Save to database using sqlor-database-module + """Save a specific setting value to current user's database record.""" + import json + + # Get current user ID + user_id = await get_current_user_id() + + # Load existing settings or start with defaults + settings = await get_setting() + + # Update the specific setting + if section not in settings: + settings[section] = {} + settings[section][key] = value + + try: + # Save to database using sqlor-database-module env = ServerEnv() async with get_sor_context(env, 'hermes-web-cli') as sor: - sql_template = SETTINGS_CRUD['operations']['create_or_update']['sql_template'] - await sor.sqlExe(sql_template, { - 'user_id': user_id, - 'settings_json': json.dumps(settings) - }) - return True - except Exception as e: - print(f"Error saving settings: {str(e)}") - return False + sql_template = SETTINGS_CRUD['operations']['create_or_update']['sql_template'] + await sor.sqlExe(sql_template, { + 'user_id': user_id, + 'settings_json': json.dumps(settings) + }) + return True + except Exception as e: + print(f"Error saving settings: {str(e)}") + return False # Module metadata MODULE_NAME = "hermes-web-cli" @@ -566,23 +566,23 @@ MODULE_VERSION = "0.1.0" # Export all public functions __all__ = [ - 'load_hermes_web_cli', - 'get_all_services', - 'create_service', - 'delete_service', - 'get_service_by_id', - 'test_service_connection', - 'create_session', - 'send_message_to_service', - 'get_session_messages', - 'get_active_sessions', - 'get_recent_sessions', - 'get_session_by_id', - 'validate_service_url', - 'generate_session_id', - 'get_setting', - 'save_setting', - 'get_current_user_id', - 'MODULE_NAME', - 'MODULE_VERSION' + 'load_hermes_web_cli', + 'get_all_services', + 'create_service', + 'delete_service', + 'get_service_by_id', + 'test_service_connection', + 'create_session', + 'send_message_to_service', + 'get_session_messages', + 'get_active_sessions', + 'get_recent_sessions', + 'get_session_by_id', + 'validate_service_url', + 'generate_session_id', + 'get_setting', + 'save_setting', + 'get_current_user_id', + 'MODULE_NAME', + 'MODULE_VERSION' ]