Add WebSocket support to browser IO using FastAPI #126

Merged
Copilot merged 9 commits from copilot/add-web-socket-support into update-v0.42.0 2025-11-26 15:23:37 -05:00
Copilot commented 2025-11-22 15:04:41 -05:00 (Migrated from github.com)

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.

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 ```bash # 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 <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> > > ---- > > *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) > > <comments> > </comments> > </details> - Fixes neph1/LlamaTale#111 <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
neph1 (Migrated from github.com) requested changes 2025-11-23 01:36:37 -05:00
neph1 (Migrated from github.com) left a comment

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

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
Copilot commented 2025-11-23 01:39:33 -05:00 (Migrated from github.com)

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.

> 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.
neph1 commented 2025-11-23 02:06:53 -05:00 (Migrated from github.com)

@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?

@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?
Copilot commented 2025-11-23 02:09:11 -05:00 (Migrated from github.com)

@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".

> @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 commented 2025-11-26 15:17:58 -05:00 (Migrated from github.com)

@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?

@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?
Copilot commented 2025-11-26 15:21:07 -05:00 (Migrated from github.com)

@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.

> @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 (Migrated from github.com) approved these changes 2025-11-26 15:22:40 -05:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
neph1/LlamaTale!126
No description provided.