Files
codex/codex-rs/codex-api/src/endpoint/responses_websocket/connector.rs
T
dob-oai b538d2b340 Preserve WebSocket probe evidence and redact diagnostic details (#57597)
## Why

Handshake probes can lose upgrade evidence when a subsequent read fails, and diagnostic output can expose URLs, server error messages, and close reasons containing sensitive values.

## What changed

- Retain successful upgrade status and allowlisted tracking headers, including on post-upgrade read failures.
- Return structured probe errors that distinguish typed TLS failures from failures with an unknown stage while preserving the underlying API error.
- Recognize empty close frames as immediate closes with code `1005`.
- Redact wire values from probe debug output and doctor error details, and remove URLs and raw errors from connection logs.

## Testing

Add local TLS and WebSocket fixtures covering certificate rejection, ordinary I/O failures, successful and rejected upgrades, immediate closes, malformed frames, header retention, and redaction. Add doctor output snapshots and error-detail redaction assertions.

GitOrigin-RevId: 1ee53720a7a752043943170d8e4da205ec4e55f3
2026-10-27 23:51:43 +00:00

51 lines
2.0 KiB
Rust

//! Establishes Responses sockets with caller-owned TLS and proxy configuration.
//! Connector reuse never caches authentication or bypasses per-connection destination policy.
use super::ResponsesWebsocketClient;
use super::ResponsesWebsocketConnection;
use super::connect_websocket;
use super::merge_request_headers;
use crate::ApiError;
use crate::WebsocketTelemetry;
use codex_websocket_client::WebSocketConnector;
use http::HeaderMap;
use std::sync::Arc;
use std::sync::OnceLock;
impl ResponsesWebsocketClient {
/// Opens a socket using the caller's reusable connector and this client's current auth.
/// The caller owns the lifetime of the connector's TLS configuration snapshot.
#[tracing::instrument(
name = "responses_websocket.connect",
level = "info",
skip_all,
fields(transport = "responses_websocket", api.path = "/responses")
)]
pub async fn connect_with_connector(
&self,
connector: &WebSocketConnector,
extra_headers: HeaderMap,
default_headers: HeaderMap,
turn_state: Option<Arc<OnceLock<String>>>,
telemetry: Option<Arc<dyn WebsocketTelemetry>>,
) -> Result<ResponsesWebsocketConnection, ApiError> {
let ws_url = self
.provider
.websocket_url_for_path("/responses")
.map_err(|err| ApiError::Stream(format!("failed to build websocket URL: {err}")))?;
let headers = merge_request_headers(&self.provider.headers, extra_headers, default_headers);
let headers = self.auth.apply_auth_headers(headers).await?;
let (stream, _response, server_reasoning_included, server_model) =
connect_websocket(ws_url, headers, connector, turn_state)
.await
.map_err(|failure| *failure.error)?;
Ok(ResponsesWebsocketConnection::new(
stream,
self.provider.stream_idle_timeout,
server_reasoning_included,
server_model,
telemetry,
))
}
}