mirror of
https://github.com/openai/codex.git
synced 2026-11-07 20:30: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
@@ -231,6 +231,55 @@ async fn activation_preserves_native_settings_and_thread_metadata() {
|
||||
assert!(server.requests.try_recv().is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prewarm_encodes_blank_and_project_managed_environments() {
|
||||
let mut fixture = fixture_responses(vec![
|
||||
response(vec![frame(&[])], tonic::Status::ok("")),
|
||||
response(vec![frame(&[])], tonic::Status::ok("")),
|
||||
])
|
||||
.await;
|
||||
|
||||
assert!(matches!(
|
||||
fixture
|
||||
.client
|
||||
.prewarm_default_thread(/*model*/ None, /*model_provider*/ None)
|
||||
.await,
|
||||
Err(Error::InvalidResponse)
|
||||
));
|
||||
assert_request(&mut fixture, "Prewarm", b"\x52\x04\x0a\x02\x0a\x00").await;
|
||||
|
||||
assert!(matches!(
|
||||
fixture
|
||||
.client
|
||||
.prewarm_thread(
|
||||
/*model*/ None,
|
||||
/*model_provider*/ None,
|
||||
Some(ProjectEnvironment {
|
||||
name: "App".to_owned(),
|
||||
cwd: Some("/workspace/acme/app".to_owned()),
|
||||
origin_urls: vec![
|
||||
"git@github.com:acme/app.git".to_owned(),
|
||||
"https://github.com/acme/lib.git".to_owned(),
|
||||
],
|
||||
}),
|
||||
)
|
||||
.await,
|
||||
Err(Error::InvalidResponse)
|
||||
));
|
||||
let expected = [
|
||||
b"\x52\x73\x0a\x71\x0a\x6f\x0a\x0d" as &[u8],
|
||||
b"asenvcfg_base",
|
||||
b"\x12\x13/workspace/acme/app",
|
||||
b"\x1a\x49\x0a\x42\x0a\x1d\x2a\x1b",
|
||||
b"git@github.com:acme/app.git",
|
||||
b"\x0a\x21\x2a\x1f",
|
||||
b"https://github.com/acme/lib.git",
|
||||
b"\x12\x03App",
|
||||
]
|
||||
.concat();
|
||||
assert_request(&mut fixture, "Prewarm", &expected).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prewarm_uses_managed_defaults_and_preserves_resolved_settings() {
|
||||
// PrewarmThread uses Thread field numbers but omits turns entirely.
|
||||
|
||||
@@ -56,6 +56,7 @@ pub use items::CommandSummary;
|
||||
pub use items::ItemLifecycle;
|
||||
pub use items::ItemStatus;
|
||||
pub use items::ItemSummary;
|
||||
pub use prewarm::ProjectEnvironment;
|
||||
pub use transcript::FileUpdate;
|
||||
pub use transcript::ItemDelta;
|
||||
pub use transcript::PatchKind;
|
||||
|
||||
@@ -6,6 +6,22 @@ use bytes::Bytes;
|
||||
use bytes::BytesMut;
|
||||
use prost::Message;
|
||||
|
||||
/// A local project's GitHub origins to provision in the default cloud environment.
|
||||
/// Callers must supply credential-free origin URLs; this client does not inspect local Git state.
|
||||
#[derive(Clone)]
|
||||
pub struct ProjectEnvironment {
|
||||
pub name: String,
|
||||
pub origin_urls: Vec<String>,
|
||||
/// Working directory inside the managed environment, when a project selects one.
|
||||
pub cwd: Option<String>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for ProjectEnvironment {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str("ProjectEnvironment([redacted])")
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Prewarm an ordinary coding thread in the default managed environment.
|
||||
/// The service makes it discoverable when it admits the first user message.
|
||||
@@ -17,6 +33,39 @@ impl Client {
|
||||
model: Option<String>,
|
||||
model_provider: Option<String>,
|
||||
) -> Result<crate::ThreadActivation, Error> {
|
||||
self.prewarm_thread(model, model_provider, /*project*/ None)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Prewarm a coding thread with an optional project checkout.
|
||||
pub async fn prewarm_thread(
|
||||
&self,
|
||||
model: Option<String>,
|
||||
model_provider: Option<String>,
|
||||
project: Option<ProjectEnvironment>,
|
||||
) -> Result<crate::ThreadActivation, Error> {
|
||||
let managed = match project {
|
||||
Some(ProjectEnvironment {
|
||||
name,
|
||||
origin_urls,
|
||||
cwd,
|
||||
}) => wire::ManagedEnvironment {
|
||||
environment_config_id: Some("asenvcfg_base".to_owned()),
|
||||
cwd,
|
||||
overrides: Some(wire::EnvironmentOverrides {
|
||||
repositories: Some(wire::RepositoryOverrides {
|
||||
values: origin_urls
|
||||
.into_iter()
|
||||
.map(|origin_url| wire::RepositoryOverride {
|
||||
origin_url: Some(origin_url),
|
||||
})
|
||||
.collect(),
|
||||
}),
|
||||
name: Some(name),
|
||||
}),
|
||||
},
|
||||
None => wire::ManagedEnvironment::default(),
|
||||
};
|
||||
let response = self
|
||||
.raw_unary(
|
||||
wire::PrewarmRequest {
|
||||
@@ -24,7 +73,7 @@ impl Client {
|
||||
model_provider,
|
||||
environments: Some(wire::EnvironmentList {
|
||||
values: vec![wire::EnvironmentSelection {
|
||||
managed: Some(wire::ManagedEnvironment {}),
|
||||
managed: Some(managed),
|
||||
}],
|
||||
}),
|
||||
},
|
||||
@@ -79,7 +128,31 @@ mod wire {
|
||||
pub managed: Option<ManagedEnvironment>,
|
||||
}
|
||||
#[derive(Clone, Message)]
|
||||
pub(super) struct ManagedEnvironment {}
|
||||
pub(super) struct ManagedEnvironment {
|
||||
#[prost(string, optional, tag = "1")]
|
||||
pub environment_config_id: Option<String>,
|
||||
#[prost(string, optional, tag = "2")]
|
||||
pub cwd: Option<String>,
|
||||
#[prost(message, optional, tag = "3")]
|
||||
pub overrides: Option<EnvironmentOverrides>,
|
||||
}
|
||||
#[derive(Clone, Message)]
|
||||
pub(super) struct EnvironmentOverrides {
|
||||
#[prost(message, optional, tag = "1")]
|
||||
pub repositories: Option<RepositoryOverrides>,
|
||||
#[prost(string, optional, tag = "2")]
|
||||
pub name: Option<String>,
|
||||
}
|
||||
#[derive(Clone, Message)]
|
||||
pub(super) struct RepositoryOverrides {
|
||||
#[prost(message, repeated, tag = "1")]
|
||||
pub values: Vec<RepositoryOverride>,
|
||||
}
|
||||
#[derive(Clone, Message)]
|
||||
pub(super) struct RepositoryOverride {
|
||||
#[prost(string, optional, tag = "5")]
|
||||
pub origin_url: Option<String>,
|
||||
}
|
||||
#[derive(Clone, Message)]
|
||||
pub(super) struct PrewarmResponse {
|
||||
#[prost(message, optional, tag = "1")]
|
||||
|
||||
Reference in New Issue
Block a user