-
Notifications
You must be signed in to change notification settings - Fork 153
Permalink
Choose a base ref
{{ refName }}
default
Choose a head ref
{{ refName }}
default
Checking mergeability…
Don’t worry, you can still create the pull request.
Comparing changes
Choose two branches to see what’s changed or to start a new pull request.
If you need to, you can also or
learn more about diff comparisons.
Open a pull request
Create a new pull request by comparing changes across two branches. If you need to, you can also .
Learn more about diff comparisons here.
base repository: WebexCommunity/WebexPythonSDK
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: ashjorda-patch
Could not load branches
Nothing to show
Loading
Could not load tags
Nothing to show
{{ refName }}
default
Loading
...
head repository: WebexCommunity/WebexPythonSDK
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Could not load branches
Nothing to show
Loading
Could not load tags
Nothing to show
{{ refName }}
default
Loading
- 12 commits
- 7 files changed
- 4 contributors
Commits on Sep 17, 2025
-
Fixes #256 with Thread-Aware Message Retrieval
jozanini committedSep 17, 2025 Configuration menu - View commit details
-
Copy full SHA for c71fd77 - Browse repository at this point
Copy the full SHA c71fd77View commit details
Commits on Oct 9, 2025
-
Removed duplicate horizontalAlignment from simple_properties and descriptive comments in ColumnSet class. Added horizontalAlignment to simple_properties of Container class.
Configuration menu - View commit details
-
Copy full SHA for 415674b - Browse repository at this point
Copy the full SHA 415674bView commit details
Commits on Dec 10, 2025
-
Configuration menu - View commit details
-
Copy full SHA for 89c5327 - Browse repository at this point
Copy the full SHA 89c5327View commit details
Commits on Mar 24, 2026
-
Fixes #256 with Thread-Aware Message Retrieval (#266)
# Issue #256 Solution: Thread-Aware Message Retrieval ## Problem Description ### Original Issue The Webex Python SDK had a critical limitation where thread message retrieval worked correctly in 1:1 conversations but failed in spaces (group rooms) with the following errors: 1. **404 Not Found Error**: `api.messages.get(parent_id)` worked for 1:1 conversations but failed in spaces 2. **403 Forbidden Error**: `api.messages.list(roomId=room_id, beforeMessage=parent_id)` worked for 1:1 but failed in spaces ### Root Cause Analysis The issue was caused by different permission models and API limitations between: - **Direct rooms (1:1 conversations)**: Messages are directly accessible via message ID - **Group rooms (spaces)**: Messages have different access controls and may require different retrieval strategies ### Impact This limitation prevented bots and applications from reliably retrieving thread context in spaces, making it impossible to: - Access the root message of a thread in spaces - Collect complete thread conversations for processing - Provide proper context to AI/LLM systems when responding to threaded messages ## Solution Overview ### Approach Implemented a **multi-strategy, room-type-aware message retrieval system** that: 1. Detects room type (direct vs group) automatically 2. Uses appropriate API endpoints based on room type 3. Implements robust fallback mechanisms when direct retrieval fails 4. Provides comprehensive error handling and user feedback ### Key Components #### 1. New API Methods (`src/webexpythonsdk/api/messages.py`) **Room Type Detection:** ```python def _is_direct_room(self, message): """Determine if a message is from a direct (1:1) room.""" def _is_group_room(self, message): """Determine if a message is from a group room (space).""" ``` **Thread Retrieval:** ```python def get_thread_messages(self, message, max_scan=500): """Retrieve all messages in a thread, including the root message.""" # Returns: (thread_messages, root_message, error_message) def get_thread_context(self, message, max_scan=500): """Get comprehensive thread context information.""" # Returns: dict with thread_messages, root_message, reply_count, etc. ``` #### 2. Utility Function (`src/webexpythonsdk/thread_utils.py`) **Drop-in Replacement:** ```python def collect_thread_text_and_attachments(api, msg, max_scan=500, max_chars=60000): """Robustly collect thread text + attachments for both 1:1 and spaces.""" # Returns: (thread_text, [attachment_text]) ``` #### 3. Multi-Strategy Retrieval **Strategy 1: Direct Retrieval** - Attempts `api.messages.get(parent_id)` first - Works for most cases when bot has proper permissions **Strategy 2: Room-Type-Aware Fallback** - **Direct rooms**: Uses `list_direct()` with `parentId` parameter - **Group rooms**: Scans recent messages to find parent by ID **Strategy 3: Reply Collection** - **Direct rooms**: Uses `list_direct()` for thread replies - **Group rooms**: Uses `list()` with `parentId` parameter **Strategy 4: Error Handling** - Provides clear error messages when retrieval fails - Graceful degradation to single message processing - Informative feedback about permission limitations ## Implementation Details ### File Structure ``` src/webexpythonsdk/ ├── api/ │ └── messages.py # Enhanced with thread-aware methods ├── thread_utils.py # New utility functions └── __init__.py # Updated exports tests/ ├── api/ │ └── test_messages.py # Real integration tests └── (thread_utils tests integrated into test_messages.py) examples/ └── thread_example.py # Usage examples docs/ └── THREAD_UTILS_README.md # Comprehensive documentation ``` ### API Method Details #### `get_thread_messages(message, max_scan=500)` **Purpose**: Core thread retrieval method with robust error handling **Parameters**: - `message`: Message object to get thread for - `max_scan`: Maximum messages to scan when searching for parent **Returns**: - `thread_messages`: List of all messages in thread (oldest to newest) - `root_message`: The root message of the thread (or None if not found) - `error_message`: Error description if any issues occurred #### `get_thread_context(message, max_scan=500)` **Purpose**: Convenience method returning structured thread information **Returns**: ```python { "thread_messages": [...], # List of messages in thread "root_message": message, # Root message object "reply_count": 5, # Number of replies "is_thread": True, # Boolean indicating if threaded "error": None, # Error message if any "room_type": "group" # Type of room (direct/group) } ``` ### Error Handling #### Common Error Scenarios 1. **404 Not Found**: Parent message not accessible - **Cause**: Bot joined after thread started or lacks permission - **Handling**: Automatic fallback to scanning recent messages 2. **403 Forbidden**: Insufficient permissions - **Cause**: Bot doesn't have access to space messages - **Handling**: Graceful degradation with informative error messages 3. **API Exceptions**: Network or API errors - **Cause**: Temporary API issues - **Handling**: Fallback to single message processing #### Error Messages - `"Could not retrieve parent message {id}. Bot may have joined after thread started or lacks permission."` - `"Could not retrieve thread replies: {error}"` - `"Failed to retrieve thread context: {error}"` ## Usage Examples ### Basic Usage (Drop-in Replacement) ```python # Old way (user's original implementation) # thread_text, attachments = your_collect_thread_text_and_attachments(msg) # New way (using the SDK utility) from webexpythonsdk.thread_utils import collect_thread_text_and_attachments thread_text, attachments = collect_thread_text_and_attachments(api, msg) ``` ### Advanced Usage (More Control) ```python # Get detailed thread information context = api.messages.get_thread_context(message) if context['error']: print(f"Error: {context['error']}") else: print(f"Thread has {len(context['thread_messages'])} messages") print(f"Room type: {context['room_type']}") print(f"Reply count: {context['reply_count']}") # Process each message in the thread for msg in context['thread_messages']: print(f"[{msg.personId}]: {msg.text}") ``` ### Error Handling ```python try: context = api.messages.get_thread_context(message) if context['error']: if "permission" in context['error'].lower(): print("Bot lacks permission to access thread root") elif "joined after" in context['error'].lower(): print("Bot joined after thread started") else: print(f"Other error: {context['error']}") else: print("Thread retrieved successfully") except Exception as e: print(f"Unexpected error: {e}") ``` ## Testing ### Test Coverage - **Unit Tests**: Mock-based tests integrated into `test_messages.py` - **Integration Tests**: Real API tests in `test_messages.py` - **Error Scenarios**: Comprehensive error handling validation - **Room Types**: Both direct and group room testing - **Edge Cases**: Single messages, invalid data, permission errors ### Test Categories 1. **Room Type Detection**: Verifies correct identification of direct vs group rooms 2. **Thread Context**: Tests comprehensive thread information retrieval 3. **Thread Messages**: Tests core message collection functionality 4. **Error Handling**: Validates graceful error handling and fallback behavior 5. **Utility Functions**: Tests drop-in replacement functionality 6. **Parameter Validation**: Tests custom parameters and limits ## Migration Guide ### For Existing Code 1. **Import the new function**: ```python from webexpythonsdk.thread_utils import collect_thread_text_and_attachments ``` 2. **Replace your function call**: ```python # Old way # thread_text, attachments = your_collect_thread_text_and_attachments(msg) # New way thread_text, attachments = collect_thread_text_and_attachments(api, msg) ``` 3. **Update error handling** (optional): The new function provides better error messages and handles both room types automatically. ### For New Code Use the new API methods directly for more control: ```python # Get thread context context = api.messages.get_thread_context(message) # Check if it's a thread if context['is_thread']: print(f"Processing thread with {context['reply_count']} replies") # Process each message for msg in context['thread_messages']: process_message(msg) else: print("Single message, not a thread") ``` ## Performance Considerations - **Max Scan Limit**: Default 500 messages to prevent excessive API calls - **Caching**: Author display names are cached to reduce API calls - **Pagination**: Uses efficient pagination for large threads - **Truncation**: Automatic text truncation to prevent memory issues - **Rate Limiting**: Respects Webex API rate limits ## Limitations 1. **File Attachments**: The utility functions include placeholder implementations for file processing 2. **Display Names**: Uses placeholder display names; integrate with People API for real names 3. **Rate Limits**: Respects Webex API rate limits but doesn't implement backoff ## Future Enhancements Potential improvements for future versions: 1. Real People API integration for display names 2. File attachment processing 3. Rate limiting and backoff strategies 4. Thread analytics and metrics 5. Real-time thread updates ## Files Modified/Created ### New Files - `src/webexpythonsdk/thread_utils.py` - Utility functions - `tests/api/test_messages.py` - Integration and unit tests - `examples/thread_example.py` - Usage examples - `THREAD_UTILS_README.md` - Comprehensive documentation - `ISSUE_256_SOLUTION.md` - This documentation ### Modified Files - `src/webexpythonsdk/api/messages.py` - Added thread-aware methods - `src/webexpythonsdk/__init__.py` - Updated exports - `tests/api/test_messages.py` - Added integration tests ## Conclusion This solution provides a robust, room-type-aware thread message retrieval system that resolves the original 404/403 errors while maintaining backward compatibility. The implementation includes comprehensive error handling, extensive testing, and clear documentation to ensure reliable operation in both 1:1 conversations and spaces. The solution is production-ready and provides a simple migration path for existing code while offering advanced features for new implementations. --- **Issue**: #256 **Status**: ✅ Resolved **Implementation Date**: 2024 **SDK Version**: Compatible with existing versions
Configuration menu - View commit details
-
Copy full SHA for 82c90f8 - Browse repository at this point
Copy the full SHA 82c90f8View commit details -
Configuration menu - View commit details
-
Copy full SHA for 9e09dcd - Browse repository at this point
Copy the full SHA 9e09dcdView commit details -
updating ci to fix build issue
jozanini committedMar 24, 2026 Configuration menu - View commit details
-
Copy full SHA for 6e3cde2 - Browse repository at this point
Copy the full SHA 6e3cde2View commit details -
Configuration menu - View commit details
-
Copy full SHA for 26b6659 - Browse repository at this point
Copy the full SHA 26b6659View commit details -
Configuration menu - View commit details
-
Copy full SHA for 8f372f3 - Browse repository at this point
Copy the full SHA 8f372f3View commit details -
Removed duplicate horizontalAlignment from simple_properties and descriptive comments in ColumnSet class. Added horizontalAlignment to simple_properties of Container class.
Configuration menu - View commit details
-
Copy full SHA for 3478f34 - Browse repository at this point
Copy the full SHA 3478f34View commit details -
This is a behavior fix (correct data binding), not a new feature. Anything that already called these properties will start getting different, correct values.
Configuration menu - View commit details
-
Copy full SHA for f521100 - Browse repository at this point
Copy the full SHA f521100View commit details
Commits on Apr 14, 2026
-
Enhance room update functionality in tests
Updated the room creation in `add_rooms` to store the created room object directly. Modified the `test_update_room_title` to include an `isLocked` parameter during the room update, ensuring the room's locked state is validated in the assertions.
jozanini committedApr 14, 2026 Configuration menu - View commit details
-
Copy full SHA for e3193e5 - Browse repository at this point
Copy the full SHA e3193e5View commit details -
Enhance room update functionality in tests (#272)
Updated the room creation in `add_rooms` to store the created room object directly. Modified the `test_update_room_title` to include an `isLocked` parameter during the room update, ensuring the room's locked state is validated in the assertions.
Configuration menu - View commit details
-
Copy full SHA for 5aea148 - Browse repository at this point
Copy the full SHA 5aea148View commit details
Loading
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff ashjorda-patch...main