Reauthenticate runtime requests before reusing WebSocket access (#60055)

## Why

An open WebSocket can remain usable after its runtime is paused. Runtime-authenticated requests need a fresh handshake to revalidate access before submitting more work.

## What changed

- Reconnect before each runtime-authenticated Responses request and skip speculative preconnection. Preserve turn routing when the runtime identity is unchanged.
- Open a fresh connection for each runtime-authenticated Guardian classification instead of prewarming or reusing pooled sockets.
- Preserve HTTP fallback for unsupported upgrades and connection failures, while propagating authentication, authorization, rate-limit, and policy errors.

## Testing

Add regression coverage for pause and unpause behavior, credential changes, fresh assertions, and Guardian HTTP and WebSocket transports in both runtime authentication modes.

GitOrigin-RevId: 9f650147260da5cbbf5bb23dfff0af095fff3955
This commit is contained in:
cooper-oai
2026-11-07 04:06:26 +00:00
committed by copyberry
parent 44ccf39457
commit d4a341bd52
7 changed files with 513 additions and 49 deletions
+9 -3
View File
@@ -316,7 +316,8 @@ pub struct ModelClient {
/// A turn-scoped streaming session created from a [`ModelClient`].
///
/// The session establishes a Responses WebSocket connection lazily and reuses it across multiple
/// requests within the turn. It also caches per-turn state:
/// requests within the turn. Runtime-authenticated requests reconnect between requests to
/// revalidate the registered credential source. It also caches per-turn state:
///
/// - The last full request, so subsequent calls can reuse incremental websocket request payloads
/// only when the current request is an incremental extension of the previous one.
@@ -1745,7 +1746,10 @@ impl ModelClientSession {
session_telemetry: &SessionTelemetry,
responses_metadata: &CodexResponsesMetadata,
) -> Result<()> {
if !self.client.responses_websocket_enabled() {
// Runtime admission belongs to the request that will use the connection.
if !self.client.responses_websocket_enabled()
|| self.client.state.provider.runtime_auth().is_some()
{
return Ok(());
}
let provider = Arc::clone(&self.client.state.provider);
@@ -1860,8 +1864,10 @@ impl ModelClientSession {
};
let connection_key = ResponsesConnectionKey::new(&api_provider, auth_revision);
let reset_reason = match self.websocket_session.connection.as_ref() {
// Reauthenticate before each runtime request; an open socket cannot prove liveness.
Some(_)
if self.websocket_session.responses_headers != *responses_headers
if runtime_id.is_some()
|| self.websocket_session.responses_headers != *responses_headers
|| self.websocket_session.connection_key.as_ref() != Some(&connection_key)
|| self.websocket_session.runtime_id != runtime_id
|| self.websocket_session.parent_routing_affinity