mirror of
https://github.com/openai/codex.git
synced 2026-09-07 15:40:00 +00:00
feat(core): send attestation for ChatGPT compaction and realtime setup
This commit is contained in:
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -60,6 +60,9 @@ jobs:
|
||||
PACK_OUTPUT="${OUTPUT_DIR}/codex-npm-${CODEX_VERSION}.tgz"
|
||||
echo "pack_output=$PACK_OUTPUT" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Test Darwin arm64 npm staging
|
||||
run: python3 codex-cli/scripts/test_build_npm_package.py
|
||||
|
||||
- name: Upload staged npm package artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
with:
|
||||
|
||||
61
codex-cli/scripts/test_build_npm_package.py
Normal file
61
codex-cli/scripts/test_build_npm_package.py
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Focused tests for Codex CLI npm package staging."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
BUILD_SCRIPT = SCRIPT_DIR / "build_npm_package.py"
|
||||
APPLE_SILICON_TARGET = "aarch64-apple-darwin"
|
||||
|
||||
|
||||
class BuildNpmPackageTests(unittest.TestCase):
|
||||
def test_darwin_arm64_package_includes_devicecheck_probe(self) -> None:
|
||||
with tempfile.TemporaryDirectory(prefix="codex-npm-test-") as tmp_dir_str:
|
||||
tmp_dir = Path(tmp_dir_str)
|
||||
vendor_src = tmp_dir / "vendor"
|
||||
target_dir = vendor_src / APPLE_SILICON_TARGET
|
||||
|
||||
(target_dir / "codex").mkdir(parents=True)
|
||||
(target_dir / "codex" / "codex").touch()
|
||||
(target_dir / "devicecheck-probe" / "DeviceCheckProbe.app").mkdir(parents=True)
|
||||
(target_dir / "path").mkdir(parents=True)
|
||||
(target_dir / "path" / "rg").touch()
|
||||
|
||||
staging_dir = tmp_dir / "stage"
|
||||
subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(BUILD_SCRIPT),
|
||||
"--package",
|
||||
"codex-darwin-arm64",
|
||||
"--version",
|
||||
"0.0.0-test",
|
||||
"--staging-dir",
|
||||
str(staging_dir),
|
||||
"--vendor-src",
|
||||
str(vendor_src),
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
staged_target_dir = staging_dir / "vendor" / APPLE_SILICON_TARGET
|
||||
self.assertTrue((staged_target_dir / "codex" / "codex").exists())
|
||||
self.assertTrue(
|
||||
(
|
||||
staged_target_dir
|
||||
/ "devicecheck-probe"
|
||||
/ "DeviceCheckProbe.app"
|
||||
).is_dir()
|
||||
)
|
||||
self.assertTrue((staged_target_dir / "path" / "rg").exists())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -118,6 +118,7 @@ use codex_login::auth_env_telemetry::AuthEnvTelemetry;
|
||||
use codex_login::auth_env_telemetry::collect_auth_env_telemetry;
|
||||
use codex_model_provider::SharedModelProvider;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_model_provider_info::CHATGPT_CODEX_BASE_URL;
|
||||
#[cfg(test)]
|
||||
use codex_model_provider_info::DEFAULT_WEBSOCKET_CONNECT_TIMEOUT_MS;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
@@ -424,6 +425,8 @@ impl ModelClient {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let client_setup = self.current_client_setup().await?;
|
||||
let should_send_attestation =
|
||||
should_send_attestation(&client_setup.api_provider, AttestationPurpose::Compaction);
|
||||
let transport = ReqwestTransport::new(build_reqwest_client());
|
||||
let request_telemetry = Self::build_request_telemetry(
|
||||
session_telemetry,
|
||||
@@ -477,6 +480,9 @@ impl ModelClient {
|
||||
extra_headers.extend(build_conversation_headers(Some(
|
||||
self.state.conversation_id.to_string(),
|
||||
)));
|
||||
if should_send_attestation && let Some(header_value) = macos_devicecheck_header() {
|
||||
extra_headers.insert(X_OAI_ATTESTATION_HEADER, header_value);
|
||||
}
|
||||
let trace_attempt = compaction_trace.start_attempt(&payload);
|
||||
let result = client
|
||||
.compact_input(&payload, extra_headers)
|
||||
@@ -495,10 +501,18 @@ impl ModelClient {
|
||||
// Create the media call over HTTP first, then retain matching auth so realtime can attach
|
||||
// the server-side control WebSocket to the call id from that HTTP response.
|
||||
let client_setup = self.current_client_setup().await?;
|
||||
let should_send_attestation = should_send_attestation(
|
||||
&client_setup.api_provider,
|
||||
AttestationPurpose::RealtimeWebrtcCallSetup,
|
||||
);
|
||||
let mut sideband_headers = extra_headers.clone();
|
||||
sideband_headers.extend(sideband_websocket_auth_headers(
|
||||
client_setup.api_auth.as_ref(),
|
||||
));
|
||||
let mut extra_headers = extra_headers;
|
||||
if should_send_attestation && let Some(header_value) = macos_devicecheck_header() {
|
||||
extra_headers.insert(X_OAI_ATTESTATION_HEADER, header_value);
|
||||
}
|
||||
let transport = ReqwestTransport::new(build_reqwest_client());
|
||||
let response =
|
||||
ApiRealtimeCallClient::new(transport, client_setup.api_provider, client_setup.api_auth)
|
||||
@@ -1627,18 +1641,32 @@ fn build_responses_headers(
|
||||
headers
|
||||
}
|
||||
|
||||
fn provider_base_url_is_chatgpt_codex(provider: &codex_api::Provider) -> bool {
|
||||
provider
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum AttestationPurpose {
|
||||
Response,
|
||||
Compaction,
|
||||
RealtimeWebrtcCallSetup,
|
||||
}
|
||||
|
||||
fn should_send_attestation(provider: &codex_api::Provider, purpose: AttestationPurpose) -> bool {
|
||||
let provider_is_chatgpt_codex = provider
|
||||
.base_url
|
||||
.trim_end_matches('/')
|
||||
.eq_ignore_ascii_case("https://chatgpt.com/backend-api/codex")
|
||||
.eq_ignore_ascii_case(CHATGPT_CODEX_BASE_URL);
|
||||
provider_is_chatgpt_codex
|
||||
&& matches!(
|
||||
purpose,
|
||||
AttestationPurpose::Response
|
||||
| AttestationPurpose::Compaction
|
||||
| AttestationPurpose::RealtimeWebrtcCallSetup
|
||||
)
|
||||
}
|
||||
|
||||
fn extend_devicecheck_header_for_responses_endpoint(
|
||||
headers: &mut ApiHeaderMap,
|
||||
provider: &codex_api::Provider,
|
||||
) {
|
||||
if provider_base_url_is_chatgpt_codex(provider)
|
||||
if should_send_attestation(provider, AttestationPurpose::Response)
|
||||
&& let Some(header_value) = macos_devicecheck_header()
|
||||
{
|
||||
headers.insert(X_OAI_ATTESTATION_HEADER, header_value);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use super::AttestationPurpose;
|
||||
use super::AttestationPurpose;
|
||||
use super::AuthRequestTelemetryContext;
|
||||
use super::ModelClient;
|
||||
use super::PendingUnauthorizedRetry;
|
||||
@@ -8,7 +10,9 @@ use super::X_CODEX_TURN_METADATA_HEADER;
|
||||
use super::X_CODEX_WINDOW_ID_HEADER;
|
||||
use super::X_OPENAI_SUBAGENT_HEADER;
|
||||
use codex_api::ApiError;
|
||||
use codex_api::Provider as ApiProvider;
|
||||
use codex_api::ResponseEvent;
|
||||
use codex_api::RetryConfig as ApiRetryConfig;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
use codex_model_provider::BearerAuthProvider;
|
||||
use codex_model_provider_info::WireApi;
|
||||
@@ -383,3 +387,43 @@ fn auth_request_telemetry_context_tracks_attached_auth_and_retry_phase() {
|
||||
assert_eq!(auth_context.recovery_mode, Some("managed"));
|
||||
assert_eq!(auth_context.recovery_phase, Some("refresh_token"));
|
||||
}
|
||||
|
||||
fn api_provider(base_url: &str) -> ApiProvider {
|
||||
ApiProvider {
|
||||
name: "test".to_string(),
|
||||
base_url: base_url.to_string(),
|
||||
query_params: None,
|
||||
headers: http::HeaderMap::new(),
|
||||
retry: ApiRetryConfig {
|
||||
max_attempts: 1,
|
||||
base_delay: Duration::from_millis(1),
|
||||
retry_429: false,
|
||||
retry_5xx: true,
|
||||
retry_transport: true,
|
||||
},
|
||||
stream_idle_timeout: Duration::from_secs(1),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_send_attestation_for_allowed_chatgpt_codex_purposes() {
|
||||
let provider = api_provider("https://chatgpt.com/backend-api/codex/");
|
||||
|
||||
for purpose in [
|
||||
AttestationPurpose::Response,
|
||||
AttestationPurpose::Compaction,
|
||||
AttestationPurpose::RealtimeWebrtcCallSetup,
|
||||
] {
|
||||
assert!(super::should_send_attestation(&provider, purpose));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_send_attestation_for_non_chatgpt_codex_provider() {
|
||||
let provider = api_provider("https://api.openai.com/v1");
|
||||
|
||||
assert!(!super::should_send_attestation(
|
||||
&provider,
|
||||
AttestationPurpose::Response,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ const MAX_REQUEST_MAX_RETRIES: u64 = 100;
|
||||
|
||||
const OPENAI_PROVIDER_NAME: &str = "OpenAI";
|
||||
pub const OPENAI_PROVIDER_ID: &str = "openai";
|
||||
pub const CHATGPT_CODEX_BASE_URL: &str = "https://chatgpt.com/backend-api/codex";
|
||||
const AMAZON_BEDROCK_PROVIDER_NAME: &str = "Amazon Bedrock";
|
||||
pub const AMAZON_BEDROCK_PROVIDER_ID: &str = "amazon-bedrock";
|
||||
pub const AMAZON_BEDROCK_DEFAULT_BASE_URL: &str =
|
||||
@@ -234,7 +235,7 @@ impl ModelProviderInfo {
|
||||
auth_mode,
|
||||
Some(AuthMode::Chatgpt | AuthMode::ChatgptAuthTokens | AuthMode::AgentIdentity)
|
||||
) {
|
||||
"https://chatgpt.com/backend-api/codex"
|
||||
CHATGPT_CODEX_BASE_URL
|
||||
} else {
|
||||
"https://api.openai.com/v1"
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user