Use pinned Codex version for provider requests
This commit is contained in:
parent
d419a6f0e1
commit
5c966c1fbb
1 changed files with 74 additions and 5 deletions
|
|
@ -114,13 +114,17 @@ impl CodexProviderBackend {
|
|||
&self,
|
||||
invocation: &CodexTransportInvocation,
|
||||
) -> Result<CodexTransportResult, CodexProviderBackendError> {
|
||||
let (first, user_agent) = {
|
||||
let (first, user_agent, client_version) = {
|
||||
let mut authority = self
|
||||
.authority
|
||||
.lock()
|
||||
.map_err(|_| CodexProviderBackendError::CredentialAuthorityPoisoned)?;
|
||||
let credential = authority.read_credential(false)?;
|
||||
(credential, authority.app_server_user_agent.clone())
|
||||
(
|
||||
credential,
|
||||
authority.app_server_user_agent.clone(),
|
||||
authority.codex_cli_version.clone(),
|
||||
)
|
||||
};
|
||||
let body = provider_http_body(&invocation.request, first.mode)?;
|
||||
let first_digest = first.auth_file_sha256;
|
||||
|
|
@ -131,12 +135,13 @@ impl CodexProviderBackend {
|
|||
&body,
|
||||
&first,
|
||||
&user_agent,
|
||||
&client_version,
|
||||
);
|
||||
let response = match response {
|
||||
Ok(response)
|
||||
if response.status().as_u16() == 401 && first.mode == CodexAuthMode::Chatgpt =>
|
||||
{
|
||||
let (refreshed, user_agent) = {
|
||||
let (refreshed, user_agent, client_version) = {
|
||||
let mut authority = self
|
||||
.authority
|
||||
.lock()
|
||||
|
|
@ -147,7 +152,11 @@ impl CodexProviderBackend {
|
|||
} else {
|
||||
authority.read_credential(true)?
|
||||
};
|
||||
(credential, authority.app_server_user_agent.clone())
|
||||
(
|
||||
credential,
|
||||
authority.app_server_user_agent.clone(),
|
||||
authority.codex_cli_version.clone(),
|
||||
)
|
||||
};
|
||||
if refreshed.auth_file_sha256 == first_digest {
|
||||
return Ok(failed_result(
|
||||
|
|
@ -164,6 +173,7 @@ impl CodexProviderBackend {
|
|||
&refreshed_body,
|
||||
&refreshed,
|
||||
&user_agent,
|
||||
&client_version,
|
||||
)
|
||||
}
|
||||
result => result,
|
||||
|
|
@ -271,6 +281,7 @@ fn send_provider_request(
|
|||
body: &[u8],
|
||||
credential: &CodexCredentialSnapshot,
|
||||
user_agent: &str,
|
||||
client_version: &str,
|
||||
) -> Result<ureq::http::Response<ureq::Body>, ureq::Error> {
|
||||
let authorization = Zeroizing::new(format!("Bearer {}", credential.bearer.as_str()));
|
||||
let mut request = http
|
||||
|
|
@ -282,7 +293,7 @@ fn send_provider_request(
|
|||
.header("user-agent", user_agent)
|
||||
.header("session_id", &invocation.request.conversation_id)
|
||||
.header("x-client-request-id", invocation.request_id())
|
||||
.header("version", env!("CARGO_PKG_VERSION"));
|
||||
.header("version", client_version);
|
||||
if let Some(account_id) = &credential.account_id {
|
||||
request = request.header("ChatGPT-Account-ID", account_id);
|
||||
}
|
||||
|
|
@ -562,6 +573,7 @@ struct CodexAppServerAuthority {
|
|||
rpc: AppServerRpc<BufReader<ChildStdout>, ChildStdin>,
|
||||
codex_home: PathBuf,
|
||||
app_server_user_agent: String,
|
||||
codex_cli_version: String,
|
||||
}
|
||||
|
||||
impl CodexAppServerAuthority {
|
||||
|
|
@ -625,11 +637,19 @@ impl CodexAppServerAuthority {
|
|||
return Err(CodexProviderBackendError::CodexHomeIdentity);
|
||||
}
|
||||
|
||||
let codex_cli_version = match codex_cli_version_from_user_agent(&initialized.user_agent) {
|
||||
Ok(version) => version,
|
||||
Err(error) => {
|
||||
terminate_child(&mut child);
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
child,
|
||||
rpc,
|
||||
codex_home: expected_home,
|
||||
app_server_user_agent: initialized.user_agent,
|
||||
codex_cli_version,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -644,6 +664,27 @@ impl CodexAppServerAuthority {
|
|||
}
|
||||
}
|
||||
|
||||
fn codex_cli_version_from_user_agent(
|
||||
user_agent: &str,
|
||||
) -> Result<String, CodexProviderBackendError> {
|
||||
let prefix = format!("{CONNECTOR_ORIGINATOR}/");
|
||||
let first = user_agent
|
||||
.split_whitespace()
|
||||
.next()
|
||||
.ok_or(CodexProviderBackendError::AppServerVersionIdentity)?;
|
||||
let version = first
|
||||
.strip_prefix(&prefix)
|
||||
.ok_or(CodexProviderBackendError::AppServerVersionIdentity)?;
|
||||
if version.is_empty()
|
||||
|| !version
|
||||
.bytes()
|
||||
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'-' | b'+'))
|
||||
{
|
||||
return Err(CodexProviderBackendError::AppServerVersionIdentity);
|
||||
}
|
||||
Ok(version.to_string())
|
||||
}
|
||||
|
||||
impl Drop for CodexAppServerAuthority {
|
||||
fn drop(&mut self) {
|
||||
terminate_child(&mut self.child);
|
||||
|
|
@ -962,6 +1003,8 @@ pub enum CodexProviderBackendError {
|
|||
CodexHomeIdentity,
|
||||
#[error("Codex app-server protocol failed")]
|
||||
AppServerProtocol,
|
||||
#[error("Codex app-server did not report its pinned CLI version")]
|
||||
AppServerVersionIdentity,
|
||||
#[error("Codex app-server IO failed")]
|
||||
AppServerIo(#[source] io::Error),
|
||||
#[error("Codex app-server refused the authentication request")]
|
||||
|
|
@ -1033,6 +1076,29 @@ mod tests {
|
|||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn provider_version_comes_from_the_pinned_codex_app_server_identity() {
|
||||
assert_eq!(
|
||||
codex_cli_version_from_user_agent(
|
||||
"gamecult_codex_connector/0.150.0-alpha.7 (Debian 13.0.0; x86_64) unknown (gamecult_codex_connector; 0.1.0)"
|
||||
)
|
||||
.unwrap(),
|
||||
"0.150.0-alpha.7"
|
||||
);
|
||||
assert!(matches!(
|
||||
codex_cli_version_from_user_agent("codex_cli_rs/0.150.0-alpha.7"),
|
||||
Err(CodexProviderBackendError::AppServerVersionIdentity)
|
||||
));
|
||||
assert!(matches!(
|
||||
codex_cli_version_from_user_agent("gamecult_codex_connector/0.150.0 bad version"),
|
||||
Ok(version) if version == "0.150.0"
|
||||
));
|
||||
assert!(matches!(
|
||||
codex_cli_version_from_user_agent("gamecult_codex_connector/not/a/version"),
|
||||
Err(CodexProviderBackendError::AppServerVersionIdentity)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_key_readiness_exposes_no_credential_identity() {
|
||||
let bytes = br#"{
|
||||
|
|
@ -1223,6 +1289,7 @@ mod tests {
|
|||
&expected_body,
|
||||
&credential,
|
||||
"codex/test",
|
||||
"0.150.0-alpha.7",
|
||||
)
|
||||
.unwrap();
|
||||
let parsed = parse_responses_sse(response.into_body().into_reader()).unwrap();
|
||||
|
|
@ -1244,6 +1311,8 @@ mod tests {
|
|||
assert!(headers.contains("originator: gamecult_codex_connector"));
|
||||
assert!(headers.contains("session_id: conversation-http"));
|
||||
assert!(headers.contains("x-client-request-id: request-http"));
|
||||
assert!(headers.contains("version: 0.150.0-alpha.7"));
|
||||
assert!(!headers.contains(concat!("version: ", env!("CARGO_PKG_VERSION"))));
|
||||
assert_eq!(&received[headers_end + 4..], expected_body);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue