From eb1473d3e7c249c3355d641cb84db6f640b26bdf Mon Sep 17 00:00:00 2001 From: Elias Posen Date: Wed, 8 Jul 2026 12:52:33 -0400 Subject: [PATCH 1/2] feat(pctx-py): replace deprecated api_key with arbitrary headers The system that validated the pctx api_key is no longer maintained. Replace the `api_key` client parameter with `headers: dict[str, str]`, an arbitrary set of headers applied to every HTTP request and the WebSocket connection request. No backwards-compatible shim is retained. The reserved `x-code-mode-session` header is applied after caller headers on the WebSocket connect so it cannot be clobbered. Co-Authored-By: Claude Opus 4.8 (1M context) --- pctx-py/src/pctx_client/_client.py | 10 ++++++---- pctx-py/src/pctx_client/_websocket_client.py | 7 ++++--- pctx-py/tests/scripts/manual_code_mode.py | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pctx-py/src/pctx_client/_client.py b/pctx-py/src/pctx_client/_client.py index 6aed4cc..2f320c5 100644 --- a/pctx-py/src/pctx_client/_client.py +++ b/pctx-py/src/pctx_client/_client.py @@ -65,7 +65,7 @@ def __init__( tools: list[Tool | AsyncTool] | None = None, servers: list[ServerConfig] | None = None, url: str = "http://localhost:8080", - api_key: str | None = None, + headers: dict[str, str] | None = None, execute_timeout: float = 30.0, ): """ @@ -77,6 +77,8 @@ def __init__( - HTTP server: {"name": "...", "url": "...", "auth": {...}} - stdio server: {"name": "...", "command": "...", "args": [...], "env": {...}} url: PCTX server URL (default: http://localhost:8080) + headers: Additional headers applied to every request and the + WebSocket connection request execute_timeout: Timeout for code execution in seconds (default: 30.0) """ @@ -100,14 +102,14 @@ def __init__( ws_scheme = "wss" if http_scheme == "https" else "ws" self._ws_client = WebSocketClient( - url=f"{ws_scheme}://{host}{parsed.path}/ws", api_key=api_key, tools=tools + url=f"{ws_scheme}://{host}{parsed.path}/ws", headers=headers, tools=tools ) self._client = AsyncClient( base_url=f"{http_scheme}://{host}{parsed.path}", - headers={"x-pctx-api-key": api_key or ""}, + headers=headers or {}, ) self._session_id: str | None = None - self._api_key = api_key + self._headers = headers or {} self._tools = tools or [] self._servers = servers or [] diff --git a/pctx-py/src/pctx_client/_websocket_client.py b/pctx-py/src/pctx_client/_websocket_client.py index f076228..c50e5de 100644 --- a/pctx-py/src/pctx_client/_websocket_client.py +++ b/pctx-py/src/pctx_client/_websocket_client.py @@ -52,7 +52,7 @@ class WebSocketClient: def __init__( self, url: str, - api_key: str | None = None, + headers: dict[str, str] | None = None, tools: list[Tool | AsyncTool] | None = None, ): """ @@ -60,11 +60,12 @@ def __init__( Args: url: WebSocket server URL (e.g., "ws://localhost:8080/ws") + headers: Additional headers to send with the connection request """ self.url = url self.ws: ClientConnection | None = None self.tools = tools or [] - self._api_key = api_key + self._headers = headers or {} self._pending_executions: dict[str | int, asyncio.Future] = {} self._request_counter = 0 @@ -77,8 +78,8 @@ async def _connect(self, code_mode_session: str): """ try: headers = { + **self._headers, "x-code-mode-session": code_mode_session, - "x-pctx-api-key": self._api_key or "", } self.ws = await websockets.connect(self.url, additional_headers=headers) except Exception as e: diff --git a/pctx-py/tests/scripts/manual_code_mode.py b/pctx-py/tests/scripts/manual_code_mode.py index bd28261..2eed002 100755 --- a/pctx-py/tests/scripts/manual_code_mode.py +++ b/pctx-py/tests/scripts/manual_code_mode.py @@ -48,7 +48,7 @@ def multiply(a: float, b: float) -> MultiplyOutput: async def main(): async with Pctx( # url="https://....", - # api_key="pctx_xxxx", + # headers={"authorization": "Bearer xxxx"}, tools=[add, subtract, multiply, now_timestamp, search_logs], servers=[ { From 30d6f2b021699c87c54384c3368916398193ae6e Mon Sep 17 00:00:00 2001 From: Elias Posen Date: Wed, 8 Jul 2026 13:28:29 -0400 Subject: [PATCH 2/2] changelog --- pctx-py/CHANGELOG.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pctx-py/CHANGELOG.md b/pctx-py/CHANGELOG.md index 66269b2..bc4e5b3 100644 --- a/pctx-py/CHANGELOG.md +++ b/pctx-py/CHANGELOG.md @@ -11,7 +11,21 @@ For changes to the underlying Rust crates and CLI, see the ## [UNRELEASED] - YYYY-MM-DD -## [v0.4.0] - 2026-06-08 +### Added + +- `Pctx(headers=...)`: an arbitrary `dict[str, str]` of headers applied to every + HTTP request and the WebSocket connection request. Use this to authenticate + against deployments that expect custom headers (e.g. a + `{"authorization": "Bearer "}` for GCP IAM-protected services). + +### Removed + +- **Breaking**: the deprecated `Pctx(api_key=...)` parameter and the + `x-pctx-api-key` header it set. The system that validated this key is no + longer maintained. Pass credentials via `headers=` instead — e.g. + `Pctx(api_key="k")` → `Pctx(headers={"x-pctx-api-key": "k"})`. + +## [v0.4.1] - 2026-06-08 ### Added - `py.typed` for mypy