mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-17 21:25:46 +02:00
## Problem Fixes #7953. The v3 SSE and WebSocket stream transports build their default paths by interpolating `thread_id` directly into the URL: ```python self._commands_url = commands_path or f"/threads/{thread_id}/commands" self._stream_url = stream_path or f"/threads/{thread_id}/stream/events" ``` This skips the `_quote_path_param` escaping that the rest of the SDK adopted in #7893. A `thread_id` containing reserved characters or dot-segments is then normalized by the HTTP/WebSocket stack before transmission. For example, `thread_id = "../assistants/abc"`: | | before | |---|---| | constructed | `/threads/../assistants/abc/commands` | | wire path | `/assistants/abc/commands` | So the value stops being one opaque identifier under `/threads/{thread_id}/...` and silently hits a different resource. ## Fix Reuse the existing `_quote_path_param` helper for the **default** paths in all four transports (`http`, `sync_http`, `ws`, `sync_ws`). Explicit `commands_path` / `stream_path` overrides are left untouched, so callers that pass their own paths opt out of encoding as before. `_quote_path_param("../assistants/abc")` → `..%2Fassistants%2Fabc`, which the HTTP/WS stack no longer collapses. ## Tests Adds `tests/streaming/test_transport_path_encoding.py` covering all four transports: - default `_commands_url` / `_stream_url` / `_stream_path` are percent-encoded, - the actual SSE wire path (async + sync) stays under `/threads/`, - the built WebSocket URL (async + sync) stays under `/threads/`, - explicit path overrides are left untouched. `make format`, `make lint`, and `make test` all pass in `libs/sdk-py` (491 passed).