"""HTTP client for one T3 Code server (`packages/contracts/src/environmentHttp.ts`). Four routes are used: the unauthenticated descriptor, the shell read-model (projects + threads without bodies), one thread with a turn window, and `dispatch` with a `ClientOrchestrationCommand`. The WebSocket RPC (`terminal.*` and friends) is deliberately not wrapped. """ from __future__ import annotations import uuid from datetime import UTC, datetime from typing import Any import httpx DESCRIPTOR = "/.well-known/t3/environment" SHELL = "/api/orchestration/shell" THREADS = "/api/orchestration/threads" DISPATCH = "/api/orchestration/dispatch" WS_TICKET = "/api/auth/websocket-ticket" WS_PATH = "/ws" SUBSCRIBE_SHELL = "orchestration.subscribeShell" RUNTIME_MODE = "full-access" INTERACTION_MODE = "default" PROBE_TIMEOUT = 5.0 class T3Error(Exception): def __init__(self, status: int, body: Any) -> None: self.status = status self.body = body detail = ( f"{body.get('code')}: {body.get('reason') or body.get('message')}" if isinstance(body, dict) else str(body)[:300] ) super().__init__(f"T3 responded {status} - {detail}") def now_iso() -> str: return datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z") def new_id() -> str: return str(uuid.uuid4()) def thread_create( thread_id: str, project_id: str, title: str, selection: dict[str, Any] ) -> dict[str, Any]: return { "type": "thread.create", "commandId": new_id(), "threadId": thread_id, "projectId": project_id, "title": title, "modelSelection": selection, "runtimeMode": RUNTIME_MODE, "interactionMode": INTERACTION_MODE, "branch": None, "worktreePath": None, "createdAt": now_iso(), } def turn_start( thread_id: str, text: str, selection: dict[str, Any] | None = None, title_seed: str | None = None, ) -> dict[str, Any]: command: dict[str, Any] = { "type": "thread.turn.start", "commandId": new_id(), "threadId": thread_id, "message": { "messageId": new_id(), "role": "user", "text": text, "attachments": [], }, "runtimeMode": RUNTIME_MODE, "interactionMode": INTERACTION_MODE, "createdAt": now_iso(), } if selection is not None: command["modelSelection"] = selection if title_seed: command["titleSeed"] = title_seed return command def turn_interrupt(thread_id: str, turn_id: str | None = None) -> dict[str, Any]: command: dict[str, Any] = { "type": "thread.turn.interrupt", "commandId": new_id(), "threadId": thread_id, "createdAt": now_iso(), } if turn_id: command["turnId"] = turn_id return command def user_input_respond( thread_id: str, request_id: str, answers: dict[str, Any] ) -> dict[str, Any]: return { "type": "thread.user-input.respond", "commandId": new_id(), "threadId": thread_id, "requestId": request_id, "answers": answers, "createdAt": now_iso(), } class T3Client: def __init__( self, url: str, token: str, *, transport: httpx.AsyncBaseTransport | None = None, timeout: float = 30.0, ) -> None: self.url = url.rstrip("/") self._http = httpx.AsyncClient( base_url=self.url, headers={"Authorization": f"Bearer {token}"}, timeout=timeout, transport=transport, ) async def aclose(self) -> None: await self._http.aclose() async def descriptor(self) -> dict[str, Any]: response = await self._http.get( DESCRIPTOR, headers={"Authorization": ""}, timeout=PROBE_TIMEOUT ) return self._json(response) async def shell(self) -> dict[str, Any]: return self._json(await self._http.get(SHELL)) async def thread( self, thread_id: str, turn_limit: int | None = None ) -> dict[str, Any] | None: params = {"turnLimit": turn_limit} if turn_limit else None response = await self._http.get(f"{THREADS}/{thread_id}", params=params) if response.status_code == httpx.codes.NOT_FOUND: return None return self._json(response) async def dispatch(self, command: dict[str, Any]) -> dict[str, Any]: return self._json(await self._http.post(DISPATCH, json=command)) async def ws_url(self) -> str: ticket = self._json(await self._http.post(WS_TICKET))["ticket"] scheme = "wss" if self.url.startswith("https") else "ws" return f"{scheme}://{self.url.split('://', 1)[1]}{WS_PATH}?wsTicket={ticket}" @staticmethod def _json(response: httpx.Response) -> dict[str, Any]: try: body: Any = response.json() except ValueError: body = response.text if response.is_error or not isinstance(body, dict): raise T3Error(response.status_code, body) return body