mirror of
https://github.com/openai/codex.git
synced 2026-09-09 15:58:47 +00:00
## Why A thread can select skill roots that live in an executor environment. `skills/list` needs a passive snapshot of the roots that are usable now: it must not start an executor, wait for recovery, or reconnect a failed environment. The initial implementation checked the immutable first startup result. After a successful connection later entered recovery or failed, that result still looked successful. A read-only catalog request could then wait for recovery or trigger a new connection while reading the filesystem. ## What - inspect readiness from the current exec-server connection state - return roots only while their environment can serve a request immediately - omit environments that have not started, are connecting, or are recovering - return warnings for missing environments and terminal connection failures - add a fail-fast filesystem view that never starts, waits for, or reconnects an environment - expose the passive selected-root snapshot through `CodexThread` ## Behavior - Local and currently connected environments are ready. - Starting and recovering environments are omitted without a warning so callers can retry later. - Missing and terminally failed environments are omitted with a warning. - A disconnect between readiness inspection and filesystem access fails promptly instead of crossing into the normal recovery path. - Normal model-turn and execution paths keep their existing reconnect behavior. ## Design The recovery policy is private to the exec-server client. Callers choose the explicit fail-fast filesystem method; the existing client and filesystem APIs remain reconnecting. This keeps the passive contract at the transport boundary instead of plumbing timeout or retry flags through the skills stack. ## Coverage - a lazy stdio environment stays unstarted during passive inspection - missing and terminally failed environments surface warnings - a real websocket disconnect proves current readiness drops, a previously acquired fail-fast filesystem handle returns promptly, and readiness returns after recovery ## Scope This PR only provides passive readiness and fail-fast filesystem primitives. It does not add app-server API fields or notifications. ## Stack - #31582 uses these primitives for experimental thread-scoped `skills/list`. - #30228 adds targeted invalidation notifications.
93 lines
3.4 KiB
Rust
93 lines
3.4 KiB
Rust
//! JSON-RPC-backed `HttpClient` implementation.
|
|
//!
|
|
//! This code runs in the orchestrator process. It does not issue network
|
|
//! requests directly; instead it forwards `http/request` to the remote runtime
|
|
//! and then reconstructs streamed bodies from `http/request/bodyDelta`
|
|
//! notifications on the shared connection.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use futures::FutureExt;
|
|
use futures::future::BoxFuture;
|
|
use tokio::sync::mpsc;
|
|
|
|
use super::HttpResponseBodyStream;
|
|
use super::response_body_stream::HttpBodyStreamRegistration;
|
|
use crate::HttpClient;
|
|
use crate::client::ExecServerClient;
|
|
use crate::client::ExecServerError;
|
|
use crate::protocol::HTTP_REQUEST_METHOD;
|
|
use crate::protocol::HttpRequestParams;
|
|
use crate::protocol::HttpRequestResponse;
|
|
|
|
/// Maximum queued body frames per streamed HTTP response.
|
|
const HTTP_BODY_DELTA_CHANNEL_CAPACITY: usize = 256;
|
|
|
|
impl ExecServerClient {
|
|
/// Performs an HTTP request and buffers the response body.
|
|
pub async fn http_request(
|
|
&self,
|
|
mut params: HttpRequestParams,
|
|
) -> Result<HttpRequestResponse, ExecServerError> {
|
|
params.stream_response = false;
|
|
self.call(HTTP_REQUEST_METHOD, ¶ms).await
|
|
}
|
|
|
|
/// Performs an HTTP request and returns a body stream.
|
|
///
|
|
/// The method sets `stream_response` and replaces any caller-supplied
|
|
/// `request_id` with a connection-local id, so late deltas from abandoned
|
|
/// streams cannot be confused with later requests.
|
|
pub async fn http_request_stream(
|
|
&self,
|
|
mut params: HttpRequestParams,
|
|
) -> Result<(HttpRequestResponse, HttpResponseBodyStream), ExecServerError> {
|
|
let rpc_client = self.rpc_client().await?;
|
|
params.stream_response = true;
|
|
let request_id = self.inner.next_http_body_stream_request_id();
|
|
params.request_id = request_id.clone();
|
|
let (tx, rx) = mpsc::channel(HTTP_BODY_DELTA_CHANNEL_CAPACITY);
|
|
self.inner
|
|
.insert_http_body_stream(request_id.clone(), tx)
|
|
.await?;
|
|
let mut registration =
|
|
HttpBodyStreamRegistration::new(Arc::clone(&self.inner), request_id.clone());
|
|
let response = match self
|
|
.call_rpc(&rpc_client, HTTP_REQUEST_METHOD, ¶ms)
|
|
.await
|
|
{
|
|
Ok(response) => response,
|
|
Err(error) => {
|
|
self.inner.remove_http_body_stream(&request_id).await;
|
|
registration.disarm();
|
|
return Err(error);
|
|
}
|
|
};
|
|
registration.disarm();
|
|
Ok((
|
|
response,
|
|
HttpResponseBodyStream::remote(Arc::clone(&self.inner), request_id, rx),
|
|
))
|
|
}
|
|
}
|
|
|
|
impl HttpClient for ExecServerClient {
|
|
/// Orchestrator-side adapter that forwards buffered HTTP requests to the
|
|
/// remote runtime over the shared JSON-RPC connection.
|
|
fn http_request(
|
|
&self,
|
|
params: HttpRequestParams,
|
|
) -> BoxFuture<'_, Result<HttpRequestResponse, ExecServerError>> {
|
|
async move { ExecServerClient::http_request(self, params).await }.boxed()
|
|
}
|
|
|
|
/// Orchestrator-side adapter that forwards streamed HTTP requests to the
|
|
/// remote runtime and exposes body deltas as a byte stream.
|
|
fn http_request_stream(
|
|
&self,
|
|
params: HttpRequestParams,
|
|
) -> BoxFuture<'_, Result<(HttpRequestResponse, HttpResponseBodyStream), ExecServerError>> {
|
|
async move { ExecServerClient::http_request_stream(self, params).await }.boxed()
|
|
}
|
|
}
|