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 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=[ {