mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
Start cloud threads with the local GitHub project (#60007)
## What changed Pass the local project's GitHub origin to cloud prewarming so new cloud threads can provision a repository checkout. Preserve the attached local project when switching backends and resolve its origin for each thread start. Normalize supported GitHub origins to credential-free HTTPS URLs and select a repository-specific cloud working directory. Unsupported origins retain the blank environment behavior. Keep project metadata outside the app-server wire protocol, and fix HTTP Git URL parsing so query strings and fragments cannot be mistaken for authority text. ## Testing Add coverage for project routing, origin normalization and rejection, managed environment encoding, local project retention, and Git URL credential stripping. GitOrigin-RevId: 5859769965faf060461ccc1cbb3fd48f8e2f2155
This commit is contained in:
committed by
copyberry
parent
df769889c0
commit
7de8a0afc0
@@ -19,6 +19,12 @@ use tokio::sync::oneshot;
|
||||
/// Unsupported operations must return an explicit error; they must not claim success.
|
||||
pub enum CloudOperation {
|
||||
Request(Box<ClientRequest>),
|
||||
/// Host project metadata stays on the private transport, outside the app-server wire.
|
||||
StartProjectThread {
|
||||
request_id: RequestId,
|
||||
params: Box<codex_app_server_protocol::ThreadStartParams>,
|
||||
project: codex_cloud_client::ProjectEnvironment,
|
||||
},
|
||||
SubmitUserMessage(Box<TurnStartParams>),
|
||||
Notify(ClientNotification),
|
||||
Resolve {
|
||||
@@ -124,6 +130,20 @@ impl CloudClient {
|
||||
}
|
||||
|
||||
impl CloudRequestHandle {
|
||||
pub(crate) async fn start_project_thread(
|
||||
&self,
|
||||
request_id: RequestId,
|
||||
params: codex_app_server_protocol::ThreadStartParams,
|
||||
project: codex_cloud_client::ProjectEnvironment,
|
||||
) -> io::Result<RequestResult> {
|
||||
self.send(CloudOperation::StartProjectThread {
|
||||
request_id,
|
||||
params: Box::new(params),
|
||||
project,
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Submit once with a stable message ID and saved thread settings. The server
|
||||
/// starts or steers; an error can mean admission is unknown. Never auto-resend.
|
||||
pub async fn submit_user_message(
|
||||
|
||||
@@ -180,7 +180,21 @@ async fn run_worker(
|
||||
let _ = command.response.send(Ok(Ok(Value::Null)));
|
||||
return;
|
||||
}
|
||||
CloudOperation::Request(request) => Ok((*request, cloud::SubmissionMode::StartIfIdle)),
|
||||
CloudOperation::Request(request) => {
|
||||
Ok((*request, cloud::SubmissionMode::StartIfIdle, None))
|
||||
}
|
||||
CloudOperation::StartProjectThread {
|
||||
request_id,
|
||||
params,
|
||||
project,
|
||||
} => Ok((
|
||||
protocol::ClientRequest::ThreadStart {
|
||||
request_id,
|
||||
params: *params,
|
||||
},
|
||||
cloud::SubmissionMode::StartIfIdle,
|
||||
Some(project),
|
||||
)),
|
||||
CloudOperation::SubmitUserMessage(params) => Ok((
|
||||
protocol::ClientRequest::TurnStart {
|
||||
// Native admission is keyed by the caller's stable message ID.
|
||||
@@ -188,6 +202,7 @@ async fn run_worker(
|
||||
params: *params,
|
||||
},
|
||||
cloud::SubmissionMode::Steer,
|
||||
None,
|
||||
)),
|
||||
CloudOperation::Notify(_) => {
|
||||
Err(eyre!("Cloud client notifications are not supported yet."))
|
||||
@@ -197,9 +212,9 @@ async fn run_worker(
|
||||
)),
|
||||
};
|
||||
let result = match request {
|
||||
Ok((request, mode)) => tokio::select! {
|
||||
Ok((request, mode, project)) => tokio::select! {
|
||||
_ = output.closed() => return,
|
||||
result = handle_request(request, mode, requests, replies, output) => result,
|
||||
result = handle_request(request, mode, project, requests, replies, output) => result,
|
||||
},
|
||||
Err(error) => Err(error),
|
||||
};
|
||||
@@ -231,6 +246,7 @@ async fn run_worker(
|
||||
async fn handle_request(
|
||||
request: protocol::ClientRequest,
|
||||
mode: cloud::SubmissionMode,
|
||||
project: Option<cloud::ProjectEnvironment>,
|
||||
requests: &mut requests::Requests,
|
||||
replies: &mut Replies,
|
||||
output: &mpsc::UnboundedSender<AppServerEvent>,
|
||||
@@ -256,7 +272,7 @@ async fn handle_request(
|
||||
replies.admit_turn(¶ms.thread_id, output)?;
|
||||
}
|
||||
match replies
|
||||
.while_listening(requests.handle(request, mode, output), output)
|
||||
.while_listening(requests.handle(request, mode, project, output), output)
|
||||
.await?
|
||||
{
|
||||
requests::Response::Value(value) => Ok(value),
|
||||
|
||||
@@ -54,6 +54,7 @@ impl Requests {
|
||||
&mut self,
|
||||
request: protocol::ClientRequest,
|
||||
mode: cloud::SubmissionMode,
|
||||
project: Option<cloud::ProjectEnvironment>,
|
||||
output: &mpsc::UnboundedSender<crate::AppServerEvent>,
|
||||
) -> Result<Response> {
|
||||
use protocol::ClientRequest as Request;
|
||||
@@ -79,7 +80,7 @@ impl Requests {
|
||||
"Cloud conversations start with server defaults. Custom settings are not supported yet."
|
||||
);
|
||||
}
|
||||
let settings = self.client.prewarm_default_thread(model, provider).await?;
|
||||
let settings = self.client.prewarm_thread(model, provider, project).await?;
|
||||
return attach_response(settings);
|
||||
}
|
||||
Request::ThreadResume { params, .. } => {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
mod cloud;
|
||||
pub mod cloud_backend;
|
||||
pub use codex_cloud_client::ProjectEnvironment;
|
||||
mod path;
|
||||
mod remote;
|
||||
mod remote_websocket;
|
||||
@@ -713,6 +714,27 @@ impl InProcessAppServerRequestHandle {
|
||||
}
|
||||
|
||||
impl AppServerRequestHandle {
|
||||
/// Starts a native cloud thread with host project metadata without adding wire fields.
|
||||
pub async fn start_cloud_project_thread(
|
||||
&self,
|
||||
request_id: RequestId,
|
||||
params: codex_app_server_protocol::ThreadStartParams,
|
||||
project: codex_cloud_client::ProjectEnvironment,
|
||||
) -> Result<codex_app_server_protocol::ThreadStartResponse, TypedRequestError> {
|
||||
let response = match self {
|
||||
Self::Cloud(handle) => {
|
||||
handle
|
||||
.start_project_thread(request_id, params, project)
|
||||
.await
|
||||
}
|
||||
Self::InProcess(_) | Self::Remote(_) => Err(IoError::new(
|
||||
std::io::ErrorKind::Unsupported,
|
||||
"Project checkout requires the native cloud transport.",
|
||||
)),
|
||||
};
|
||||
decode_typed_response("thread/start", response)
|
||||
}
|
||||
|
||||
pub(crate) async fn resolve_server_request(
|
||||
&self,
|
||||
request_id: RequestId,
|
||||
|
||||
Reference in New Issue
Block a user