codex: Fallback to locally installed version if update fails (#44419)
Closes #43900 Release Notes: - Fallback to locally installed codex version if update fails
This commit is contained in:
@@ -1365,7 +1365,7 @@ impl ExternalAgentServer for LocalCodex {
|
||||
&mut self,
|
||||
root_dir: Option<&str>,
|
||||
extra_env: HashMap<String, String>,
|
||||
status_tx: Option<watch::Sender<SharedString>>,
|
||||
mut status_tx: Option<watch::Sender<SharedString>>,
|
||||
_new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Task<Result<(AgentServerCommand, String, Option<task::SpawnInTerminal>)>> {
|
||||
@@ -1402,58 +1402,115 @@ impl ExternalAgentServer for LocalCodex {
|
||||
let dir = paths::external_agents_dir().join(CODEX_NAME);
|
||||
fs.create_dir(&dir).await?;
|
||||
|
||||
// Find or install the latest Codex release (no update checks for now).
|
||||
let release = ::http_client::github::latest_github_release(
|
||||
let bin_name = if cfg!(windows) {
|
||||
"codex-acp.exe"
|
||||
} else {
|
||||
"codex-acp"
|
||||
};
|
||||
|
||||
let find_latest_local_version = async || -> Option<PathBuf> {
|
||||
let mut local_versions: Vec<(semver::Version, String)> = Vec::new();
|
||||
let mut stream = fs.read_dir(&dir).await.ok()?;
|
||||
while let Some(entry) = stream.next().await {
|
||||
let Ok(entry) = entry else { continue };
|
||||
let Some(file_name) = entry.file_name() else {
|
||||
continue;
|
||||
};
|
||||
let version_path = dir.join(&file_name);
|
||||
if fs.is_file(&version_path.join(bin_name)).await {
|
||||
let version_str = file_name.to_string_lossy();
|
||||
if let Ok(version) =
|
||||
semver::Version::from_str(version_str.trim_start_matches('v'))
|
||||
{
|
||||
local_versions.push((version, version_str.into_owned()));
|
||||
}
|
||||
}
|
||||
}
|
||||
local_versions.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||
local_versions.last().map(|(_, v)| dir.join(v))
|
||||
};
|
||||
|
||||
let fallback_to_latest_local_version =
|
||||
async |err: anyhow::Error| -> Result<PathBuf, anyhow::Error> {
|
||||
if let Some(local) = find_latest_local_version().await {
|
||||
log::info!(
|
||||
"Falling back to locally installed Codex version: {}",
|
||||
local.display()
|
||||
);
|
||||
Ok(local)
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
};
|
||||
|
||||
let version_dir = match ::http_client::github::latest_github_release(
|
||||
CODEX_ACP_REPO,
|
||||
true,
|
||||
false,
|
||||
http.clone(),
|
||||
)
|
||||
.await
|
||||
.context("fetching Codex latest release")?;
|
||||
{
|
||||
Ok(release) => {
|
||||
let version_dir = dir.join(&release.tag_name);
|
||||
if !fs.is_dir(&version_dir).await {
|
||||
if let Some(ref mut status_tx) = status_tx {
|
||||
status_tx.send("Installing…".into()).ok();
|
||||
}
|
||||
|
||||
let version_dir = dir.join(&release.tag_name);
|
||||
if !fs.is_dir(&version_dir).await {
|
||||
if let Some(mut status_tx) = status_tx {
|
||||
status_tx.send("Installing…".into()).ok();
|
||||
}
|
||||
|
||||
let tag = release.tag_name.clone();
|
||||
let version_number = tag.trim_start_matches('v');
|
||||
let asset_name = asset_name(version_number)
|
||||
.context("codex acp is not supported for this architecture")?;
|
||||
let asset = release
|
||||
.assets
|
||||
.into_iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.with_context(|| format!("no asset found matching `{asset_name:?}`"))?;
|
||||
// Strip "sha256:" prefix from digest if present (GitHub API format)
|
||||
let digest = asset
|
||||
.digest
|
||||
.as_deref()
|
||||
.and_then(|d| d.strip_prefix("sha256:").or(Some(d)));
|
||||
::http_client::github_download::download_server_binary(
|
||||
&*http,
|
||||
&asset.browser_download_url,
|
||||
digest,
|
||||
&version_dir,
|
||||
if cfg!(target_os = "windows") && cfg!(target_arch = "x86_64") {
|
||||
AssetKind::Zip
|
||||
let tag = release.tag_name.clone();
|
||||
let version_number = tag.trim_start_matches('v');
|
||||
let asset_name = asset_name(version_number)
|
||||
.context("codex acp is not supported for this architecture")?;
|
||||
let asset = release
|
||||
.assets
|
||||
.into_iter()
|
||||
.find(|asset| asset.name == asset_name)
|
||||
.with_context(|| {
|
||||
format!("no asset found matching `{asset_name:?}`")
|
||||
})?;
|
||||
// Strip "sha256:" prefix from digest if present (GitHub API format)
|
||||
let digest = asset
|
||||
.digest
|
||||
.as_deref()
|
||||
.and_then(|d| d.strip_prefix("sha256:").or(Some(d)));
|
||||
match ::http_client::github_download::download_server_binary(
|
||||
&*http,
|
||||
&asset.browser_download_url,
|
||||
digest,
|
||||
&version_dir,
|
||||
if cfg!(target_os = "windows") && cfg!(target_arch = "x86_64") {
|
||||
AssetKind::Zip
|
||||
} else {
|
||||
AssetKind::TarGz
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
// remove older versions
|
||||
util::fs::remove_matching(&dir, |entry| entry != version_dir)
|
||||
.await;
|
||||
version_dir
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
"Failed to download Codex release {}: {err:#}",
|
||||
release.tag_name
|
||||
);
|
||||
fallback_to_latest_local_version(err).await?
|
||||
}
|
||||
}
|
||||
} else {
|
||||
AssetKind::TarGz
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
// remove older versions
|
||||
util::fs::remove_matching(&dir, |entry| entry != version_dir).await;
|
||||
}
|
||||
|
||||
let bin_name = if cfg!(windows) {
|
||||
"codex-acp.exe"
|
||||
} else {
|
||||
"codex-acp"
|
||||
version_dir
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to fetch Codex latest release: {err:#}");
|
||||
fallback_to_latest_local_version(err).await?
|
||||
}
|
||||
};
|
||||
|
||||
let bin_path = version_dir.join(bin_name);
|
||||
anyhow::ensure!(
|
||||
fs.is_file(&bin_path).await,
|
||||
|
||||
Reference in New Issue
Block a user