mirror of
https://github.com/openai/codex.git
synced 2026-09-06 15:29:32 +00:00
Merge 6127c6a3f5 into sapling-pr-archive-bolinfest
This commit is contained in:
2
codex-rs/Cargo.lock
generated
2
codex-rs/Cargo.lock
generated
@@ -2451,10 +2451,10 @@ dependencies = [
|
||||
"codex-tui",
|
||||
"codex-utils-cli",
|
||||
"crossterm",
|
||||
"http 1.4.0",
|
||||
"owo-colors",
|
||||
"pretty_assertions",
|
||||
"ratatui",
|
||||
"reqwest 0.12.28",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"supports-color 3.0.2",
|
||||
|
||||
@@ -27,9 +27,9 @@ codex-model-provider = { workspace = true }
|
||||
codex-tui = { workspace = true }
|
||||
codex-utils-cli = { workspace = true }
|
||||
crossterm = { workspace = true, features = ["event-stream"] }
|
||||
http = { workspace = true }
|
||||
owo-colors = { workspace = true, features = ["supports-colors"] }
|
||||
ratatui = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
supports-color = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use codex_http_client::build_reqwest_client_with_custom_ca;
|
||||
use reqwest::header::CONTENT_TYPE;
|
||||
use reqwest::header::HeaderMap;
|
||||
use codex_http_client::RouteAwareClientPool;
|
||||
use http::header::CONTENT_TYPE;
|
||||
use http::header::HeaderMap;
|
||||
use std::collections::HashMap;
|
||||
use tracing::info;
|
||||
use tracing::warn;
|
||||
@@ -23,6 +23,7 @@ pub struct AutodetectSelection {
|
||||
}
|
||||
|
||||
pub async fn autodetect_environment_id(
|
||||
http: &RouteAwareClientPool,
|
||||
base_url: &str,
|
||||
headers: &HeaderMap,
|
||||
desired_label: Option<String>,
|
||||
@@ -45,7 +46,7 @@ pub async fn autodetect_environment_id(
|
||||
)
|
||||
};
|
||||
crate::append_error_log(format!("env: GET {url}"));
|
||||
match get_json::<Vec<CodeEnvironment>>(&url, headers).await {
|
||||
match get_json::<Vec<CodeEnvironment>>(http, &url, headers).await {
|
||||
Ok(mut list) => {
|
||||
crate::append_error_log(format!(
|
||||
"env: by-repo returned {} env(s) for {owner}/{repo}",
|
||||
@@ -74,8 +75,13 @@ pub async fn autodetect_environment_id(
|
||||
};
|
||||
crate::append_error_log(format!("env: GET {list_url}"));
|
||||
// Fetch and log the full environments JSON for debugging
|
||||
let http = build_reqwest_client_with_custom_ca(reqwest::Client::builder())?;
|
||||
let res = http.get(&list_url).headers(headers.clone()).send().await?;
|
||||
let res = http
|
||||
.client_for_url(&list_url)
|
||||
.await?
|
||||
.get(&list_url)
|
||||
.headers(headers.clone())
|
||||
.send()
|
||||
.await?;
|
||||
let status = res.status();
|
||||
let ct = res
|
||||
.headers()
|
||||
@@ -145,11 +151,17 @@ fn pick_environment_row(
|
||||
}
|
||||
|
||||
async fn get_json<T: serde::de::DeserializeOwned>(
|
||||
http: &RouteAwareClientPool,
|
||||
url: &str,
|
||||
headers: &HeaderMap,
|
||||
) -> anyhow::Result<T> {
|
||||
let http = build_reqwest_client_with_custom_ca(reqwest::Client::builder())?;
|
||||
let res = http.get(url).headers(headers.clone()).send().await?;
|
||||
let res = http
|
||||
.client_for_url(url)
|
||||
.await?
|
||||
.get(url)
|
||||
.headers(headers.clone())
|
||||
.send()
|
||||
.await?;
|
||||
let status = res.status();
|
||||
let ct = res
|
||||
.headers()
|
||||
@@ -254,6 +266,7 @@ fn parse_owner_repo(url: &str) -> Option<(String, String)> {
|
||||
/// List environments for the current repo(s) with a fallback to the global list.
|
||||
/// Returns a de-duplicated, sorted set suitable for the TUI modal.
|
||||
pub async fn list_environments(
|
||||
http: &RouteAwareClientPool,
|
||||
base_url: &str,
|
||||
headers: &HeaderMap,
|
||||
) -> anyhow::Result<Vec<crate::app::EnvironmentRow>> {
|
||||
@@ -274,7 +287,7 @@ pub async fn list_environments(
|
||||
base_url, "github", owner, repo
|
||||
)
|
||||
};
|
||||
match get_json::<Vec<CodeEnvironment>>(&url, headers).await {
|
||||
match get_json::<Vec<CodeEnvironment>>(http, &url, headers).await {
|
||||
Ok(list) => {
|
||||
info!("env_tui: by-repo {}:{} -> {} envs", owner, repo, list.len());
|
||||
for e in list {
|
||||
@@ -312,7 +325,7 @@ pub async fn list_environments(
|
||||
} else {
|
||||
format!("{base_url}/api/codex/environments")
|
||||
};
|
||||
match get_json::<Vec<CodeEnvironment>>(&list_url, headers).await {
|
||||
match get_json::<Vec<CodeEnvironment>>(http, &list_url, headers).await {
|
||||
Ok(list) => {
|
||||
info!("env_tui: global list -> {} envs", list.len());
|
||||
for e in list {
|
||||
|
||||
@@ -12,6 +12,10 @@ use chrono::Utc;
|
||||
use codex_cloud_tasks_client::TaskStatus;
|
||||
use codex_git_utils::current_branch_name;
|
||||
use codex_git_utils::default_branch_name;
|
||||
use codex_http_client::ClientRouteClass;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_http_client::OutboundProxyPolicy;
|
||||
use codex_http_client::RouteAwareClientPool;
|
||||
use codex_login::default_client::get_codex_user_agent;
|
||||
use owo_colors::OwoColorize;
|
||||
use owo_colors::Stream;
|
||||
@@ -38,6 +42,7 @@ struct ApplyJob {
|
||||
struct BackendContext {
|
||||
backend: Arc<dyn codex_cloud_tasks_client::CloudBackend>,
|
||||
base_url: String,
|
||||
environment_http: RouteAwareClientPool,
|
||||
}
|
||||
|
||||
async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext> {
|
||||
@@ -56,11 +61,17 @@ async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext>
|
||||
return Ok(BackendContext {
|
||||
backend: Arc::new(codex_cloud_tasks_mock_client::MockClient),
|
||||
base_url,
|
||||
environment_http: RouteAwareClientPool::new(
|
||||
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
|
||||
ClientRouteClass::Api,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
let ua = get_codex_user_agent();
|
||||
let (auth_manager, http_client_factory) = util::load_auth_manager(Some(base_url.clone())).await;
|
||||
let environment_http =
|
||||
RouteAwareClientPool::new(http_client_factory.clone(), ClientRouteClass::Api);
|
||||
let mut http =
|
||||
codex_cloud_tasks_client::HttpClient::new(base_url.clone(), http_client_factory)?
|
||||
.with_user_agent(ua);
|
||||
@@ -105,6 +116,7 @@ async fn init_backend(user_agent_suffix: &str) -> anyhow::Result<BackendContext>
|
||||
Ok(BackendContext {
|
||||
backend: Arc::new(http),
|
||||
base_url,
|
||||
environment_http,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -192,7 +204,8 @@ async fn resolve_environment_id(ctx: &BackendContext, requested: &str) -> anyhow
|
||||
}
|
||||
let normalized = util::normalize_base_url(&ctx.base_url);
|
||||
let headers = util::build_chatgpt_headers().await;
|
||||
let environments = crate::env_detect::list_environments(&normalized, &headers).await?;
|
||||
let environments =
|
||||
crate::env_detect::list_environments(&ctx.environment_http, &normalized, &headers).await?;
|
||||
if environments.is_empty() {
|
||||
return Err(anyhow!(
|
||||
"no cloud environments are available for this workspace"
|
||||
@@ -759,8 +772,11 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
.try_init();
|
||||
|
||||
info!("Launching Cloud Tasks list UI");
|
||||
let BackendContext { backend, .. } = init_backend("codex_cloud_tasks_tui").await?;
|
||||
let backend = backend;
|
||||
let BackendContext {
|
||||
backend,
|
||||
base_url,
|
||||
environment_http,
|
||||
} = init_backend("codex_cloud_tasks_tui").await?;
|
||||
|
||||
// Terminal setup
|
||||
use crossterm::ExecutableCommand;
|
||||
@@ -840,34 +856,25 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
});
|
||||
}
|
||||
// Fetch environment list in parallel so the header can show friendly names quickly.
|
||||
{
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let base_url = util::normalize_base_url(
|
||||
&std::env::var("CODEX_CLOUD_TASKS_BASE_URL")
|
||||
.unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string()),
|
||||
);
|
||||
let headers = util::build_chatgpt_headers().await;
|
||||
let res = crate::env_detect::list_environments(&base_url, &headers).await;
|
||||
let _ = tx.send(app::AppEvent::EnvironmentsLoaded(res));
|
||||
});
|
||||
}
|
||||
spawn_environment_load(tx.clone(), base_url.clone(), environment_http.clone());
|
||||
|
||||
// Try to auto-detect a likely environment id on startup and refresh if found.
|
||||
// Do this concurrently so the initial list shows quickly; on success we refetch with filter.
|
||||
{
|
||||
let tx = tx.clone();
|
||||
let base_url = base_url.clone();
|
||||
let environment_http = environment_http.clone();
|
||||
tokio::spawn(async move {
|
||||
let base_url = util::normalize_base_url(
|
||||
&std::env::var("CODEX_CLOUD_TASKS_BASE_URL")
|
||||
.unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string()),
|
||||
);
|
||||
let base_url = util::normalize_base_url(&base_url);
|
||||
// Build headers: UA + ChatGPT auth if available
|
||||
let headers = util::build_chatgpt_headers().await;
|
||||
|
||||
// Run autodetect. If it fails, we keep using "All".
|
||||
let res = crate::env_detect::autodetect_environment_id(
|
||||
&base_url, &headers, /*desired_label*/ None,
|
||||
&environment_http,
|
||||
&base_url,
|
||||
&headers,
|
||||
/*desired_label*/ None,
|
||||
)
|
||||
.await;
|
||||
let _ = tx.send(app::AppEvent::EnvironmentAutodetected(res));
|
||||
@@ -1081,18 +1088,11 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
}
|
||||
// Proactively fetch environments to resolve a friendly name for the header.
|
||||
app.env_loading = true;
|
||||
{
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let base_url = crate::util::normalize_base_url(
|
||||
&std::env::var("CODEX_CLOUD_TASKS_BASE_URL")
|
||||
.unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string()),
|
||||
);
|
||||
let headers = crate::util::build_chatgpt_headers().await;
|
||||
let res = crate::env_detect::list_environments(&base_url, &headers).await;
|
||||
let _ = tx.send(app::AppEvent::EnvironmentsLoaded(res));
|
||||
});
|
||||
}
|
||||
spawn_environment_load(
|
||||
tx.clone(),
|
||||
base_url.clone(),
|
||||
environment_http.clone(),
|
||||
);
|
||||
let _ = frame_tx.send(Instant::now());
|
||||
}
|
||||
}
|
||||
@@ -1468,13 +1468,11 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
}
|
||||
needs_redraw = true;
|
||||
if should_fetch {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let base_url = crate::util::normalize_base_url(&std::env::var("CODEX_CLOUD_TASKS_BASE_URL").unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string()));
|
||||
let headers = crate::util::build_chatgpt_headers().await;
|
||||
let res = crate::env_detect::list_environments(&base_url, &headers).await;
|
||||
let _ = tx.send(app::AppEvent::EnvironmentsLoaded(res));
|
||||
});
|
||||
spawn_environment_load(
|
||||
tx.clone(),
|
||||
base_url.clone(),
|
||||
environment_http.clone(),
|
||||
);
|
||||
}
|
||||
// Render after opening env modal to show it instantly.
|
||||
render_if_needed(&mut terminal, &mut app, &mut needs_redraw)?;
|
||||
@@ -1654,16 +1652,11 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
if app.environments.is_empty() { app.env_loading = true; app.env_error = None; }
|
||||
needs_redraw = true;
|
||||
if app.environments.is_empty() {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let base_url = crate::util::normalize_base_url(
|
||||
&std::env::var("CODEX_CLOUD_TASKS_BASE_URL")
|
||||
.unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string()),
|
||||
);
|
||||
let headers = crate::util::build_chatgpt_headers().await;
|
||||
let res = crate::env_detect::list_environments(&base_url, &headers).await;
|
||||
let _ = tx.send(app::AppEvent::EnvironmentsLoaded(res));
|
||||
});
|
||||
spawn_environment_load(
|
||||
tx.clone(),
|
||||
base_url.clone(),
|
||||
environment_http.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
KeyCode::Left => {
|
||||
@@ -1833,13 +1826,11 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
if should_fetch { app.env_loading = true; app.env_error = None; }
|
||||
needs_redraw = true;
|
||||
if should_fetch {
|
||||
let tx = tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let base_url = crate::util::normalize_base_url(&std::env::var("CODEX_CLOUD_TASKS_BASE_URL").unwrap_or_else(|_| "https://chatgpt.com/backend-api".to_string()));
|
||||
let headers = crate::util::build_chatgpt_headers().await;
|
||||
let res = crate::env_detect::list_environments(&base_url, &headers).await;
|
||||
let _ = tx.send(app::AppEvent::EnvironmentsLoaded(res));
|
||||
});
|
||||
spawn_environment_load(
|
||||
tx.clone(),
|
||||
base_url.clone(),
|
||||
environment_http.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
KeyCode::Char('n') => {
|
||||
@@ -2021,6 +2012,19 @@ pub async fn run_main(cli: Cli, _codex_linux_sandbox_exe: Option<PathBuf>) -> an
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_environment_load(
|
||||
tx: UnboundedSender<app::AppEvent>,
|
||||
base_url: String,
|
||||
http: RouteAwareClientPool,
|
||||
) {
|
||||
tokio::spawn(async move {
|
||||
let base_url = util::normalize_base_url(&base_url);
|
||||
let headers = util::build_chatgpt_headers().await;
|
||||
let result = crate::env_detect::list_environments(&http, &base_url, &headers).await;
|
||||
let _ = tx.send(app::AppEvent::EnvironmentsLoaded(result));
|
||||
});
|
||||
}
|
||||
|
||||
// extract_chatgpt_account_id moved to util.rs
|
||||
|
||||
/// Build plain-text conversation lines: a labeled user prompt followed by assistant messages.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use chrono::DateTime;
|
||||
use chrono::Local;
|
||||
use chrono::Utc;
|
||||
use reqwest::header::HeaderMap;
|
||||
use http::header::HeaderMap;
|
||||
|
||||
use codex_core::config::Config;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
@@ -70,8 +70,8 @@ pub async fn load_auth_manager(
|
||||
/// Build headers for ChatGPT-backed requests: `User-Agent`, optional `Authorization`,
|
||||
/// and optional `ChatGPT-Account-Id`.
|
||||
pub async fn build_chatgpt_headers() -> HeaderMap {
|
||||
use reqwest::header::HeaderValue;
|
||||
use reqwest::header::USER_AGENT;
|
||||
use http::header::HeaderValue;
|
||||
use http::header::USER_AGENT;
|
||||
|
||||
set_user_agent_suffix("codex_cloud_tasks_tui");
|
||||
let ua = codex_login::default_client::get_codex_user_agent();
|
||||
|
||||
@@ -243,7 +243,6 @@ deny = [
|
||||
"codex-api",
|
||||
"codex-app-server",
|
||||
"codex-app-server-daemon",
|
||||
"codex-cloud-tasks",
|
||||
"codex-core",
|
||||
"codex-core-plugins",
|
||||
"codex-exec-server",
|
||||
|
||||
Reference in New Issue
Block a user