mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
14df0e8833
## What Introduce a `CodexResponsesMetadata` struct that defines all the core metadata we send to Responses API. Example fields are `thread_id`, `turn_id`, `window_id`, etc. Going forward, `client_metadata["x-codex-turn-metadata"]` will be the canonical way Codex sends metadata to Responses API across both HTTP and websocket transports. For now, we continue to emit the existing top-level HTTP headers and top-level `client_metadata` fields from the same `CodexResponsesMetadata` struct for compatibility reasons. Also, app-server clients who specify additional `responsesapi_client_metadata` via `turn/start` and `turn/steer` will have those fields merged into `client_metadata["x-codex-turn-metadata"]`, but cannot override the reserved fields that core uses (i.e. the fields in `CodexResponsesMetadata`). ## Why Responses API request instrumentation is the source of truth for downstream Codex analytics that join requests by Codex IDs such as session, thread, turn, and context window. Before this change, those values were assembled through several request-specific paths: HTTP request bodies, websocket handshake headers, websocket `response.create` payloads, compaction requests, and the rich `x-codex-turn-metadata` envelope all had their own wiring. That made metadata propagation easy to drift across API-key/direct Responses API requests, ChatGPT-auth/proxied requests, websocket requests, and compaction requests. It also made additions like `window_id` error-prone because a field could be added to one transport projection but missed in another. ## What changed - Added `CodexResponsesMetadata` as the core-owned snapshot for Codex metadata sent to ResponsesAPI. - Render `client_metadata["x-codex-turn-metadata"]`, flat `client_metadata` projections, and direct compatibility headers from that same snapshot. - Include the known Codex-owned fields in the turn metadata blob, including installation/session/thread/turn/window IDs, request kind, lineage, sandbox/workspace metadata, timing, and compaction details. - Treat app-server `responsesapi_client_metadata` as enrichment for the Codex turn metadata blob while preventing those extras from overriding Codex-owned fields. - Use the same metadata path for normal turns, websocket prewarm, local compaction, remote v1 compaction, and remote v2 compaction. - Keep websocket connection-only preconnect metadata separate so handshakes carry compatibility identity headers without inventing a fake turn metadata blob. ## Verification - `cargo check -p codex-core` - `just fix -p codex-core`
200 lines
6.5 KiB
Rust
200 lines
6.5 KiB
Rust
//! Test-only helpers exposed for cross-crate integration tests.
|
|
//!
|
|
//! Production code should not depend on this module.
|
|
//! We prefer this to using a crate feature to avoid building multiple
|
|
//! permutations of the crate.
|
|
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
use codex_exec_server::EnvironmentManager;
|
|
use codex_extension_api::LoadUserInstructionsFuture;
|
|
use codex_extension_api::LoadedUserInstructions;
|
|
use codex_extension_api::UserInstructionsProvider;
|
|
use codex_login::AuthManager;
|
|
use codex_login::CodexAuth;
|
|
use codex_model_provider::create_model_provider;
|
|
use codex_model_provider_info::ModelProviderInfo;
|
|
use codex_models_manager::bundled_models_response;
|
|
use codex_models_manager::collaboration_mode_presets;
|
|
use codex_models_manager::manager::SharedModelsManager;
|
|
use codex_models_manager::test_support::construct_model_info_offline_for_tests;
|
|
use codex_models_manager::test_support::get_model_offline_for_tests;
|
|
use codex_protocol::ThreadId;
|
|
use codex_protocol::config_types::CollaborationModeMask;
|
|
use codex_protocol::openai_models::ModelInfo;
|
|
use codex_protocol::openai_models::ModelPreset;
|
|
use codex_protocol::protocol::SessionSource;
|
|
use once_cell::sync::Lazy;
|
|
|
|
use crate::ThreadManager;
|
|
use crate::config::Config;
|
|
use crate::responses_metadata::CodexResponsesMetadata;
|
|
use crate::responses_metadata::CodexResponsesRequestKind;
|
|
use crate::responses_metadata::subagent_header_value;
|
|
use crate::responses_metadata::subagent_metadata_kind;
|
|
use crate::thread_manager;
|
|
use crate::unified_exec;
|
|
|
|
static TEST_MODEL_PRESETS: Lazy<Vec<ModelPreset>> = Lazy::new(|| {
|
|
let mut response = bundled_models_response()
|
|
.unwrap_or_else(|err| panic!("bundled models.json should parse: {err}"));
|
|
response.models.sort_by_key(|model| model.priority);
|
|
let mut presets: Vec<ModelPreset> = response.models.into_iter().map(Into::into).collect();
|
|
ModelPreset::mark_default_by_picker_visibility(&mut presets);
|
|
presets
|
|
});
|
|
|
|
/// Test-only provider that supplies no user instructions.
|
|
#[derive(Debug, Default)]
|
|
pub struct EmptyUserInstructionsProvider;
|
|
|
|
impl UserInstructionsProvider for EmptyUserInstructionsProvider {
|
|
fn load_user_instructions(&self) -> LoadUserInstructionsFuture<'_> {
|
|
Box::pin(async { LoadedUserInstructions::default() })
|
|
}
|
|
}
|
|
|
|
pub fn set_thread_manager_test_mode(enabled: bool) {
|
|
thread_manager::set_thread_manager_test_mode_for_tests(enabled);
|
|
}
|
|
|
|
pub fn set_deterministic_process_ids(enabled: bool) {
|
|
unified_exec::set_deterministic_process_ids_for_tests(enabled);
|
|
}
|
|
|
|
pub fn auth_manager_from_auth(auth: CodexAuth) -> Arc<AuthManager> {
|
|
AuthManager::from_auth_for_testing(auth)
|
|
}
|
|
|
|
pub fn auth_manager_from_auth_with_home(auth: CodexAuth, codex_home: PathBuf) -> Arc<AuthManager> {
|
|
AuthManager::from_auth_for_testing_with_home(auth, codex_home)
|
|
}
|
|
|
|
pub fn thread_manager_with_models_provider(
|
|
auth: CodexAuth,
|
|
provider: ModelProviderInfo,
|
|
) -> ThreadManager {
|
|
ThreadManager::with_models_provider_for_tests(auth, provider)
|
|
}
|
|
|
|
pub fn thread_manager_with_models_provider_and_home(
|
|
auth: CodexAuth,
|
|
provider: ModelProviderInfo,
|
|
codex_home: PathBuf,
|
|
environment_manager: Arc<EnvironmentManager>,
|
|
) -> ThreadManager {
|
|
ThreadManager::with_models_provider_and_home_for_tests(
|
|
auth,
|
|
provider,
|
|
codex_home,
|
|
environment_manager,
|
|
)
|
|
}
|
|
|
|
pub fn thread_manager_with_models_provider_home_and_state(
|
|
auth: CodexAuth,
|
|
provider: ModelProviderInfo,
|
|
codex_home: PathBuf,
|
|
environment_manager: Arc<EnvironmentManager>,
|
|
state_db: Option<crate::StateDbHandle>,
|
|
) -> ThreadManager {
|
|
ThreadManager::with_models_provider_home_and_state_for_tests(
|
|
auth,
|
|
provider,
|
|
codex_home,
|
|
environment_manager,
|
|
state_db,
|
|
)
|
|
}
|
|
|
|
pub async fn start_thread_with_user_shell_override(
|
|
thread_manager: &ThreadManager,
|
|
config: Config,
|
|
user_shell_override: crate::shell::Shell,
|
|
) -> codex_protocol::error::Result<crate::NewThread> {
|
|
thread_manager
|
|
.start_thread_with_user_shell_override_for_tests(config, user_shell_override)
|
|
.await
|
|
}
|
|
|
|
pub async fn resume_thread_from_rollout_with_user_shell_override(
|
|
thread_manager: &ThreadManager,
|
|
config: Config,
|
|
rollout_path: PathBuf,
|
|
auth_manager: Arc<AuthManager>,
|
|
user_shell_override: crate::shell::Shell,
|
|
) -> codex_protocol::error::Result<crate::NewThread> {
|
|
thread_manager
|
|
.resume_thread_from_rollout_with_user_shell_override_for_tests(
|
|
config,
|
|
rollout_path,
|
|
auth_manager,
|
|
user_shell_override,
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub fn models_manager_with_provider(
|
|
codex_home: PathBuf,
|
|
auth_manager: Arc<AuthManager>,
|
|
provider: ModelProviderInfo,
|
|
) -> SharedModelsManager {
|
|
let provider = create_model_provider(provider, Some(auth_manager));
|
|
provider.models_manager(codex_home, /*config_model_catalog*/ None)
|
|
}
|
|
|
|
pub fn get_model_offline(model: Option<&str>) -> String {
|
|
get_model_offline_for_tests(model)
|
|
}
|
|
|
|
pub fn construct_model_info_offline(model: &str, config: &Config) -> ModelInfo {
|
|
construct_model_info_offline_for_tests(model, &config.to_models_manager_config())
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub enum TestCodexResponsesRequestKind {
|
|
Turn,
|
|
Prewarm,
|
|
WebsocketConnection,
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn responses_metadata(
|
|
installation_id: &str,
|
|
session_id: &str,
|
|
thread_id: &str,
|
|
turn_id: Option<&str>,
|
|
window_id: String,
|
|
session_source: &SessionSource,
|
|
parent_thread_id: Option<ThreadId>,
|
|
request_kind: TestCodexResponsesRequestKind,
|
|
) -> CodexResponsesMetadata {
|
|
let request_kind = match request_kind {
|
|
TestCodexResponsesRequestKind::Turn => Some(CodexResponsesRequestKind::Turn),
|
|
TestCodexResponsesRequestKind::Prewarm => Some(CodexResponsesRequestKind::Prewarm),
|
|
TestCodexResponsesRequestKind::WebsocketConnection => None,
|
|
};
|
|
CodexResponsesMetadata {
|
|
turn_id: request_kind.and(turn_id.map(ToString::to_string)),
|
|
request_kind,
|
|
parent_thread_id,
|
|
subagent_header: subagent_header_value(session_source),
|
|
subagent_kind: request_kind.and_then(|_| subagent_metadata_kind(session_source)),
|
|
..CodexResponsesMetadata::new(
|
|
installation_id.to_string(),
|
|
session_id.to_string(),
|
|
thread_id.to_string(),
|
|
window_id,
|
|
)
|
|
}
|
|
}
|
|
|
|
pub fn all_model_presets() -> &'static Vec<ModelPreset> {
|
|
&TEST_MODEL_PRESETS
|
|
}
|
|
|
|
pub fn builtin_collaboration_mode_presets() -> Vec<CollaborationModeMask> {
|
|
collaboration_mode_presets::builtin_collaboration_mode_presets()
|
|
}
|