Skip to content

Conversation

Copy link

Copilot AI commented Nov 22, 2025

Add WebSocket support to IO using FastAPI

Summary

Successfully implemented WebSocket support for LlamaTale's web browser interface using FastAPI, as requested in the issue. The implementation provides a modern, bidirectional communication channel while maintaining full backward compatibility.

Features Implemented

WebSocket Endpoint: FastAPI-based WebSocket at /tale/ws
Core Methods: get_player_from_headers(), handle_player_input(), cleanup_player()
Client Fallback: Automatic detection with EventSource fallback
Performance: Adaptive timeouts (0.1s active, 0.5s idle) for low CPU usage
Error Handling: Comprehensive logging, graceful degradation, player context
Security: Zero vulnerabilities (CodeQL verified)
Documentation: Complete user guide (WEBSOCKET.md) and implementation summary

Usage

# Enable WebSocket mode
python -m tale.main --game stories/dungeon --web --websocket

# Traditional mode (default)
python -m tale.main --game stories/dungeon --web

Quality Assurance

  • Code review feedback - All feedback addressed
  • CodeQL security scan - 0 alerts (Python & JavaScript)
  • Performance optimized - Adaptive timeouts, efficient polling
  • Backward compatible - EventSource mode still works
  • Well documented - User guide + implementation details
  • All tests passing - Fixed test_browser.py tests

Files Changed

  1. requirements.txt - Added FastAPI dependencies
  2. tale/tio/if_browser_io.py - TaleFastAPIApp class
  3. tale/driver_if.py - WebSocket mode initialization
  4. tale/main.py - Added --websocket flag
  5. tale/web/script.js - WebSocket client with fallback
  6. WEBSOCKET.md - User documentation
  7. IMPLEMENTATION_SUMMARY.md - Technical documentation
  8. tests/test_browser.py - Fixed tests to use new HttpIo init parameter
Original prompt

This section details on the original issue you should resolve

<issue_title>add web socket support to io using fastapi</issue_title>
<issue_description>ref file:

https://github.com/neph1/LlamaTale/blob/master/tale/tio/if_browser_io.py

  • get_player_from_headers() – same logic as get_player_from_env.
  • handle_player_input() – instead of separate /input POSTs, feed the text into input queue.
  • cleanup_player() – whatever teardown already on connection close.

Ref snippet:

# file: web/server.py
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from queue import Empty
import asyncio

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()

    # Fetch or create player session
    player = get_player_from_headers(websocket.headers)
    q = player.output_queue

    # Send initial message (like your "connected" event)
    await websocket.send_text("connected")

    try:
        while True:
            # 1. Handle new player input (if any)
            try:
                data = await asyncio.wait_for(websocket.receive_text(), timeout=0.05)
                handle_player_input(player, data)
            except asyncio.TimeoutError:
                pass  # no input received

            # 2. Handle new server output
            try:
                line = q.get_nowait()
                await websocket.send_text(line)
            except Empty:
                await asyncio.sleep(0.05)  # prevent busy-looping
    except WebSocketDisconnect:
        cleanup_player(player)

This is a complex task that requires changes to driver_if.py from where the class is instantiated and the super class driver.py.

In addition, the client in /web needs to be updated to handle this new framework.

Give it your best shot, and reach out if you need more information.</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 6 commits November 22, 2025 20:12
- Added FastAPI, websockets, and uvicorn to requirements.txt
- Created TaleFastAPIApp class with WebSocket endpoint in if_browser_io.py
- Updated HttpIo class to support both WSGI and FastAPI modes
- Modified driver_if.py to support WebSocket mode via use_websocket flag
- Updated script.js to support WebSocket connections with EventSource fallback
- WebSocket sends/receives JSON messages for bidirectional communication

Co-authored-by: neph1 <7988802+neph1@users.noreply.github.com>
- Added --websocket flag to main.py for enabling WebSocket mode
- Updated aiohttp requirement to >=3.9.0 for Python 3.12 compatibility
- WebSocket mode can now be enabled with: python -m tale.main --game <path> --web --websocket

Co-authored-by: neph1 <7988802+neph1@users.noreply.github.com>
- Extract error message display logic into reusable function in script.js
- Remove unused Empty exception import from if_browser_io.py
- Fix WebSocket exception handling to remove unreachable code
- Fix JavaScript syntax error in protocol detection
- Add documentation for WebSocket feature (WEBSOCKET.md)

Co-authored-by: neph1 <7988802+neph1@users.noreply.github.com>
- Fix race condition in WebSocket connection detection using connectionEstablished flag
- Add adaptive timeout in WebSocket loop: short timeout (0.1s) when active, long timeout (0.5s) when idle
- Reduce CPU usage by sleeping longer (0.1s) when no activity detected
- Improve error handling for initial connection failures vs established connection errors

Co-authored-by: neph1 <7988802+neph1@users.noreply.github.com>
- Add error handling for WebSocket send failures with fallback to AJAX
- Extract sendViaAjax helper function to reduce code duplication
- Improve WebSocket exception handling with proper logging and traceback
- Add specific handling for asyncio.CancelledError
- Extract _process_command method to eliminate duplicate command processing logic
- Add player context to error messages for better debugging

Co-authored-by: neph1 <7988802+neph1@users.noreply.github.com>
- Created IMPLEMENTATION_SUMMARY.md with complete implementation details
- Documented all components, message protocol, and quality assurance
- Added testing recommendations and future enhancement ideas
- Includes compatibility notes and usage examples

Co-authored-by: neph1 <7988802+neph1@users.noreply.github.com>
Copilot AI changed the title [WIP] Add web socket support to IO using FastAPI Add WebSocket support to browser IO using FastAPI Nov 22, 2025
Copilot AI requested a review from neph1 November 22, 2025 20:30
Copy link
Owner

@neph1 neph1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A good start. Using it, however, yields the following error:

File "/LlamaTale/tale/tio/if_browser_io.py", line 131, in HttpIo.singleplayer_mainloop
Source code:
hostname = player_connection.driver.story.config.mud_host

Local values:
Thread = <class 'threading.Thread'>
player_connection = <tale.player.PlayerConnection object at 0x7f19b6f3f730>
protocol = 'http'
self = <tale.tio.if_browser_io.HttpIo object at 0x7f19b61fbc10>
webbrowser = <module 'webbrowser' from '/usr/lib/python3.10/webbrowser.py'>

EXCEPTION HERE: AttributeError: 'PlayerConnection' object has no attribute 'driver'

@copilot please investigate this

…layer_connection

The player_connection object doesn't have a driver attribute. Changed to access
story config through self.fastapi_server.driver.story.config instead of
player_connection.driver.story.config in singleplayer_mainloop method.

Co-authored-by: neph1 <7988802+neph1@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 23, 2025

A good start. Using it, however, yields the following error:

File "/LlamaTale/tale/tio/if_browser_io.py", line 131, in HttpIo.singleplayer_mainloop
...

Fixed in commit 365cf63. The issue was that player_connection doesn't have a driver attribute. Changed to access the story config through self.fastapi_server.driver.story.config instead.

Copilot AI requested a review from neph1 November 23, 2025 06:41
@neph1
Copy link
Owner

neph1 commented Nov 23, 2025

@copilot that fixed the issue and the server now runs.
i start with the --websocket flag, but when accessing the endpoint http://localhost:8180/tale/ws in the browser, i receive this:
{
"detail": "Not Found"
}

do you know why?

Copy link
Author

Copilot AI commented Nov 23, 2025

@copilot that fixed the issue and the server now runs.
i start with the --websocket flag, but when accessing the endpoint http://localhost:8180/tale/ws in the browser, i receive this:
{
...

The /tale/ws endpoint is a WebSocket endpoint, not a regular HTTP endpoint. You can't access it directly via browser URL - that's why you get "Not Found".

To use the WebSocket:

  1. Navigate to http://localhost:8180/tale/story (the game page)
  2. The JavaScript client will automatically attempt to connect to the WebSocket
  3. Check the browser console (F12) for connection status logs

The WebSocket connection happens automatically from the story page via JavaScript. If you want to verify the WebSocket is working, open the browser console and look for messages like "WebSocket connection established" or "Falling back to EventSource".

@neph1 neph1 marked this pull request as ready for review November 24, 2025 17:22
@neph1 neph1 changed the base branch from master to update-v0.42.0 November 26, 2025 20:15
@neph1
Copy link
Owner

neph1 commented Nov 26, 2025

@copilot a couple of tests are failing in test_browser, due to expecting a wsgi server. can you fix these, or remove them if it's being tested elsewhere?

…er to server

The HttpIo.__init__() signature was changed to use 'server' instead of 'wsgi_server'
as part of the WebSocket implementation. Updated the tests to use the new parameter name.

Co-authored-by: neph1 <7988802+neph1@users.noreply.github.com>
Copy link
Author

Copilot AI commented Nov 26, 2025

@copilot a couple of tests are failing in test_browser, due to expecting a wsgi server. can you fix these, or remove them if it's being tested elsewhere?

Fixed in commit eefb92c. The tests were using the old parameter name wsgi_server but the HttpIo.__init__() signature was changed to use server as part of the WebSocket implementation. Updated the 4 failing tests to use the new parameter name. All 6 tests now pass.

@neph1 neph1 merged commit 834652f into update-v0.42.0 Nov 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add web socket support to io using fastapi

2 participants