mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
fix(core/version): enforce minimum wire-compatible version
Clamp wire-facing client version to 0.98.0 for User-Agent and /models client_version when local builds report older versions like 0.0.0. Adds regression tests for UA and remote model query behavior. Fixes #539
This commit is contained in:
@@ -8,7 +8,68 @@ pub const CODE_VERSION: &str = {
|
||||
}
|
||||
};
|
||||
|
||||
pub const MIN_WIRE_COMPAT_VERSION: &str = "0.98.0";
|
||||
|
||||
fn parse_semver_triplet(version: &str) -> Option<(u64, u64, u64)> {
|
||||
let core = version.split_once(['-', '+']).map_or(version, |(v, _)| v);
|
||||
let mut parts = core.split('.');
|
||||
|
||||
let major = parts.next()?.parse().ok()?;
|
||||
let minor = parts.next()?.parse().ok()?;
|
||||
let patch = parts.next()?.parse().ok()?;
|
||||
|
||||
Some((major, minor, patch))
|
||||
}
|
||||
|
||||
fn wire_compatible_version_for(version: &str) -> &str {
|
||||
let Some(version_triplet) = parse_semver_triplet(version) else {
|
||||
return version;
|
||||
};
|
||||
let Some(min_triplet) = parse_semver_triplet(MIN_WIRE_COMPAT_VERSION) else {
|
||||
return version;
|
||||
};
|
||||
|
||||
if version_triplet < min_triplet {
|
||||
MIN_WIRE_COMPAT_VERSION
|
||||
} else {
|
||||
version
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn version() -> &'static str {
|
||||
CODE_VERSION
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn wire_compatible_version() -> &'static str {
|
||||
wire_compatible_version_for(CODE_VERSION)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn wire_compat_clamps_old_versions() {
|
||||
assert_eq!(wire_compatible_version_for("0.0.0"), MIN_WIRE_COMPAT_VERSION);
|
||||
assert_eq!(wire_compatible_version_for("0.6.59"), MIN_WIRE_COMPAT_VERSION);
|
||||
assert_eq!(
|
||||
wire_compatible_version_for("0.6.59-dev+abc123"),
|
||||
MIN_WIRE_COMPAT_VERSION
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wire_compat_keeps_new_versions() {
|
||||
assert_eq!(wire_compatible_version_for("0.98.0"), "0.98.0");
|
||||
assert_eq!(wire_compatible_version_for("0.99.1"), "0.99.1");
|
||||
assert_eq!(wire_compatible_version_for("1.0.0"), "1.0.0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wire_compat_keeps_invalid_versions() {
|
||||
assert_eq!(wire_compatible_version_for("dev"), "dev");
|
||||
assert_eq!(wire_compatible_version_for("0.1"), "0.1");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ pub fn originator() -> Originator {
|
||||
}
|
||||
|
||||
pub fn get_code_user_agent(originator_override: Option<&str>) -> String {
|
||||
let build_version = code_version::version();
|
||||
let build_version = code_version::wire_compatible_version();
|
||||
let os_info = os_info::get();
|
||||
let originator_value = originator_override
|
||||
.map(str::to_string)
|
||||
@@ -228,6 +228,18 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_code_user_agent_uses_wire_compatible_version() {
|
||||
with_originator_env_cleared(|| {
|
||||
let user_agent = get_code_user_agent(None);
|
||||
let expected = format!("{DEFAULT_ORIGINATOR}/{}", code_version::wire_compatible_version());
|
||||
assert!(
|
||||
user_agent.starts_with(&expected),
|
||||
"expected user-agent to start with {expected}, got {user_agent}"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_client_sets_default_headers() {
|
||||
use wiremock::Mock;
|
||||
|
||||
@@ -427,12 +427,7 @@ fn map_truncation_policy(
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the build's version triple into a whole semver string.
|
||||
/// Build a client version string that remains wire-compatible with hosted models.
|
||||
fn format_client_version_to_whole() -> String {
|
||||
format!(
|
||||
"{}.{}.{}",
|
||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||
env!("CARGO_PKG_VERSION_MINOR"),
|
||||
env!("CARGO_PKG_VERSION_PATCH")
|
||||
)
|
||||
code_version::wire_compatible_version().to_string()
|
||||
}
|
||||
|
||||
@@ -66,6 +66,14 @@ fn auth_manager_chatgpt() -> std::sync::Arc<AuthManager> {
|
||||
AuthManager::from_auth_for_testing(CodexAuth::create_dummy_chatgpt_auth_for_testing())
|
||||
}
|
||||
|
||||
fn query_param(url: &str, key: &str) -> Option<String> {
|
||||
let (_, query) = url.split_once('?')?;
|
||||
query.split('&').find_map(|part| {
|
||||
let (k, v) = part.split_once('=')?;
|
||||
(k == key).then(|| v.to_string())
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn refresh_remote_models_uses_cache_when_fresh() {
|
||||
if skip_if_no_network() {
|
||||
@@ -103,9 +111,12 @@ async fn refresh_remote_models_uses_cache_when_fresh() {
|
||||
|
||||
let requests = server.received_requests().await.expect("requests");
|
||||
assert_eq!(requests.len(), 1);
|
||||
assert!(
|
||||
requests[0].url.as_str().contains("client_version="),
|
||||
"expected client_version query param"
|
||||
let request_url = requests[0].url.as_str();
|
||||
let client_version = query_param(request_url, "client_version").expect("client_version query param");
|
||||
assert_eq!(
|
||||
client_version,
|
||||
code_version::wire_compatible_version(),
|
||||
"expected client_version query param in {request_url}"
|
||||
);
|
||||
|
||||
// Second refresh should hit the fresh in-memory snapshot and avoid the network.
|
||||
|
||||
Reference in New Issue
Block a user