From ea56186c2b53a591500aba3da6d5d3302ad06598 Mon Sep 17 00:00:00 2001 From: Owen Lin Date: Fri, 9 Jan 2026 08:17:13 -0800 Subject: [PATCH] fix(app-server): set originator header from initialize JSON-RPC request (#8873) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Motivation** The `originator` header is important for codex-backend’s Responses API proxy because it identifies the real end client (codex cli, codex vscode extension, codex exec, future IDEs) and is used to categorize requests by client for our enterprise compliance API. Today the `originator` header is set by either: - the `CODEX_INTERNAL_ORIGINATOR_OVERRIDE` env var (our VSCode extension does this) - calling `set_default_originator()` which sets a global immutable singleton (`codex exec` does this) For `codex app-server`, we want the `initialize` JSON-RPC request to set that header because it is a natural place to do so. Example: ```json { "method": "initialize", "id": 0, "params": { "clientInfo": { "name": "codex_vscode", "title": "Codex VS Code Extension", "version": "0.1.0" } } } ``` and when app-server receives that request, it can call `set_default_originator()`. This is a much more natural interface than asking third party developers to set an env var. One hiccup is that `originator()` reads the global singleton and locks in the value, preventing a later `set_default_originator()` call from setting it. This would be fine but is brittle, since any codepath that calls `originator()` before app-server can process an `initialize` JSON-RPC call would prevent app-server from setting it. This was actually the case with OTEL initialization which runs on boot, but I also saw this behavior in certain tests. Instead, what we now do is: - [unchanged] If `CODEX_INTERNAL_ORIGINATOR_OVERRIDE` env var is set, `originator()` would return that value and `set_default_originator()` with some other value does NOT override it. - [new] If no env var is set, `originator()` would return the default value which is `codex_cli_rs` UNTIL `set_default_originator()` is called once, in which case it is set to the new value and becomes immutable. Later calls to `set_default_originator()` returns `SetOriginatorError::AlreadyInitialized`. **Other notes** - I updated `codex_core::otel_init::build_provider` to accepts a service name override, and app-server sends a hardcoded `codex_app_server` service name to distinguish it from `codex_cli_rs` used by default (e.g. TUI). **Next steps** - Update VSCE to set the proper value for `clientInfo.name` on `initialize` and drop the `CODEX_INTERNAL_ORIGINATOR_OVERRIDE` env var. - Delete support for `CODEX_INTERNAL_ORIGINATOR_OVERRIDE` in codex-rs. --- codex-rs/app-server/README.md | 6 +- codex-rs/app-server/src/lib.rs | 18 ++-- codex-rs/app-server/src/message_processor.rs | 23 +++++ codex-rs/app-server/tests/common/lib.rs | 1 + .../app-server/tests/common/mcp_process.rs | 36 ++++++- codex-rs/app-server/tests/suite/user_agent.rs | 5 +- .../app-server/tests/suite/v2/initialize.rs | 98 +++++++++++++++++++ codex-rs/app-server/tests/suite/v2/mod.rs | 1 + .../app-server/tests/suite/v2/turn_start.rs | 71 ++++++++++++++ codex-rs/core/src/default_client.rs | 48 ++++++--- codex-rs/core/src/otel_init.rs | 6 +- codex-rs/core/src/rollout/recorder.rs | 2 +- codex-rs/exec/src/lib.rs | 2 +- codex-rs/tui/src/lib.rs | 2 +- codex-rs/tui2/src/lib.rs | 2 +- 15 files changed, 294 insertions(+), 27 deletions(-) create mode 100644 codex-rs/app-server/tests/suite/v2/initialize.rs diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 04e97ed62..597f002c0 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -52,6 +52,10 @@ Clients must send a single `initialize` request before invoking any other method Applications building on top of `codex app-server` should identify themselves via the `clientInfo` parameter. +**Important**: `clientInfo.name` is used to identify the client for the OpenAI Compliance Logs Platform. If +you are developing a new Codex integration that is intended for enterprise use, please contact us to get it +added to a known clients list. For more context: https://chatgpt.com/admin/api-reference#tag/Logs:-Codex + Example (from OpenAI's official VSCode extension): ```json @@ -60,7 +64,7 @@ Example (from OpenAI's official VSCode extension): "id": 0, "params": { "clientInfo": { - "name": "codex-vscode", + "name": "codex_vscode", "title": "Codex VS Code Extension", "version": "0.1.0" } diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 68663a991..c3a3e54ff 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -92,13 +92,17 @@ pub async fn run_main( let feedback = CodexFeedback::new(); - let otel = - codex_core::otel_init::build_provider(&config, env!("CARGO_PKG_VERSION")).map_err(|e| { - std::io::Error::new( - ErrorKind::InvalidData, - format!("error loading otel config: {e}"), - ) - })?; + let otel = codex_core::otel_init::build_provider( + &config, + env!("CARGO_PKG_VERSION"), + Some("codex_app_server"), + ) + .map_err(|e| { + std::io::Error::new( + ErrorKind::InvalidData, + format!("error loading otel config: {e}"), + ) + })?; // Install a simple subscriber so `tracing` output is visible. Users can // control the log level with `RUST_LOG`. diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 60e938bb1..1f442b995 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -21,8 +21,10 @@ use codex_core::AuthManager; use codex_core::ThreadManager; use codex_core::config::Config; use codex_core::config_loader::LoaderOverrides; +use codex_core::default_client::SetOriginatorError; use codex_core::default_client::USER_AGENT_SUFFIX; use codex_core::default_client::get_codex_user_agent; +use codex_core::default_client::set_default_originator; use codex_feedback::CodexFeedback; use codex_protocol::protocol::SessionSource; use toml::Value as TomlValue; @@ -121,6 +123,27 @@ impl MessageProcessor { title: _title, version, } = params.client_info; + if let Err(error) = set_default_originator(name.clone()) { + match error { + SetOriginatorError::InvalidHeaderValue => { + let error = JSONRPCErrorError { + code: INVALID_REQUEST_ERROR_CODE, + message: format!( + "Invalid clientInfo.name: '{name}'. Must be a valid HTTP header value." + ), + data: None, + }; + self.outgoing.send_error(request_id, error).await; + return; + } + SetOriginatorError::AlreadyInitialized => { + // No-op. This is expected to happen if the originator is already set via env var. + // TODO(owen): Once we remove support for CODEX_INTERNAL_ORIGINATOR_OVERRIDE, + // this will be an unexpected state and we can return a JSON-RPC error indicating + // internal server error. + } + } + } let user_agent_suffix = format!("{name}; {version}"); if let Ok(mut suffix) = USER_AGENT_SUFFIX.lock() { *suffix = Some(user_agent_suffix); diff --git a/codex-rs/app-server/tests/common/lib.rs b/codex-rs/app-server/tests/common/lib.rs index 594bb78c2..af4982b84 100644 --- a/codex-rs/app-server/tests/common/lib.rs +++ b/codex-rs/app-server/tests/common/lib.rs @@ -17,6 +17,7 @@ pub use core_test_support::format_with_current_shell_non_login; pub use core_test_support::test_path_buf_with_windows; pub use core_test_support::test_tmp_path; pub use core_test_support::test_tmp_path_buf; +pub use mcp_process::DEFAULT_CLIENT_NAME; pub use mcp_process::McpProcess; pub use mock_model_server::create_mock_responses_server_repeating_assistant; pub use mock_model_server::create_mock_responses_server_sequence; diff --git a/codex-rs/app-server/tests/common/mcp_process.rs b/codex-rs/app-server/tests/common/mcp_process.rs index 36880228d..76d436301 100644 --- a/codex-rs/app-server/tests/common/mcp_process.rs +++ b/codex-rs/app-server/tests/common/mcp_process.rs @@ -66,6 +66,8 @@ pub struct McpProcess { pending_messages: VecDeque, } +pub const DEFAULT_CLIENT_NAME: &str = "codex-app-server-tests"; + impl McpProcess { pub async fn new(codex_home: &Path) -> anyhow::Result { Self::new_with_env(codex_home, &[]).await @@ -138,7 +140,7 @@ impl McpProcess { pub async fn initialize(&mut self) -> anyhow::Result<()> { let params = Some(serde_json::to_value(InitializeParams { client_info: ClientInfo { - name: "codex-app-server-tests".to_string(), + name: DEFAULT_CLIENT_NAME.to_string(), title: None, version: "0.1.0".to_string(), }, @@ -163,6 +165,38 @@ impl McpProcess { Ok(()) } + /// Sends initialize with the provided client info and returns the response/error message. + pub async fn initialize_with_client_info( + &mut self, + client_info: ClientInfo, + ) -> anyhow::Result { + let params = Some(serde_json::to_value(InitializeParams { client_info })?); + let request_id = self.send_request("initialize", params).await?; + let request_id = RequestId::Integer(request_id); + + loop { + let message = self.read_jsonrpc_message().await?; + match message { + JSONRPCMessage::Notification(notification) => { + self.enqueue_user_message(notification); + } + JSONRPCMessage::Response(response) => { + if response.id == request_id { + return Ok(JSONRPCMessage::Response(response)); + } + } + JSONRPCMessage::Error(error) => { + if error.id == request_id { + return Ok(JSONRPCMessage::Error(error)); + } + } + JSONRPCMessage::Request(_) => { + anyhow::bail!("unexpected JSONRPCMessage::Request: {message:?}"); + } + } + } + } + /// Send a `newConversation` JSON-RPC request. pub async fn send_new_conversation_request( &mut self, diff --git a/codex-rs/app-server/tests/suite/user_agent.rs b/codex-rs/app-server/tests/suite/user_agent.rs index 5ed6cafde..9178a3ef5 100644 --- a/codex-rs/app-server/tests/suite/user_agent.rs +++ b/codex-rs/app-server/tests/suite/user_agent.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use app_test_support::DEFAULT_CLIENT_NAME; use app_test_support::McpProcess; use app_test_support::to_response; use codex_app_server_protocol::GetUserAgentResponse; @@ -25,13 +26,13 @@ async fn get_user_agent_returns_current_codex_user_agent() -> Result<()> { .await??; let os_info = os_info::get(); - let originator = codex_core::default_client::originator().value.as_str(); + let originator = DEFAULT_CLIENT_NAME; let os_type = os_info.os_type(); let os_version = os_info.version(); let architecture = os_info.architecture().unwrap_or("unknown"); let terminal_ua = codex_core::terminal::user_agent(); let user_agent = format!( - "{originator}/0.0.0 ({os_type} {os_version}; {architecture}) {terminal_ua} (codex-app-server-tests; 0.1.0)" + "{originator}/0.0.0 ({os_type} {os_version}; {architecture}) {terminal_ua} ({DEFAULT_CLIENT_NAME}; 0.1.0)" ); let received: GetUserAgentResponse = to_response(response)?; diff --git a/codex-rs/app-server/tests/suite/v2/initialize.rs b/codex-rs/app-server/tests/suite/v2/initialize.rs new file mode 100644 index 000000000..08b7766fd --- /dev/null +++ b/codex-rs/app-server/tests/suite/v2/initialize.rs @@ -0,0 +1,98 @@ +use anyhow::Result; +use app_test_support::McpProcess; +use app_test_support::to_response; +use codex_app_server_protocol::ClientInfo; +use codex_app_server_protocol::InitializeResponse; +use codex_app_server_protocol::JSONRPCMessage; +use pretty_assertions::assert_eq; +use tempfile::TempDir; +use tokio::time::timeout; + +const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn initialize_uses_client_info_name_as_originator() -> Result<()> { + let codex_home = TempDir::new()?; + let mut mcp = McpProcess::new(codex_home.path()).await?; + + let message = timeout( + DEFAULT_READ_TIMEOUT, + mcp.initialize_with_client_info(ClientInfo { + name: "codex_vscode".to_string(), + title: Some("Codex VS Code Extension".to_string()), + version: "0.1.0".to_string(), + }), + ) + .await??; + + let JSONRPCMessage::Response(response) = message else { + anyhow::bail!("expected initialize response, got {message:?}"); + }; + let InitializeResponse { user_agent } = to_response::(response)?; + + assert!(user_agent.starts_with("codex_vscode/")); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn initialize_respects_originator_override_env_var() -> Result<()> { + let codex_home = TempDir::new()?; + let mut mcp = McpProcess::new_with_env( + codex_home.path(), + &[( + "CODEX_INTERNAL_ORIGINATOR_OVERRIDE", + Some("codex_originator_via_env_var"), + )], + ) + .await?; + + let message = timeout( + DEFAULT_READ_TIMEOUT, + mcp.initialize_with_client_info(ClientInfo { + name: "codex_vscode".to_string(), + title: Some("Codex VS Code Extension".to_string()), + version: "0.1.0".to_string(), + }), + ) + .await??; + + let JSONRPCMessage::Response(response) = message else { + anyhow::bail!("expected initialize response, got {message:?}"); + }; + let InitializeResponse { user_agent } = to_response::(response)?; + + assert!(user_agent.starts_with("codex_originator_via_env_var/")); + Ok(()) +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn initialize_rejects_invalid_client_name() -> Result<()> { + let codex_home = TempDir::new()?; + let mut mcp = McpProcess::new_with_env( + codex_home.path(), + &[("CODEX_INTERNAL_ORIGINATOR_OVERRIDE", None)], + ) + .await?; + + let message = timeout( + DEFAULT_READ_TIMEOUT, + mcp.initialize_with_client_info(ClientInfo { + name: "bad\rname".to_string(), + title: Some("Bad Client".to_string()), + version: "0.1.0".to_string(), + }), + ) + .await??; + + let JSONRPCMessage::Error(error) = message else { + anyhow::bail!("expected initialize error, got {message:?}"); + }; + + assert_eq!(error.error.code, -32600); + assert_eq!( + error.error.message, + "Invalid clientInfo.name: 'bad\rname'. Must be a valid HTTP header value." + ); + assert_eq!(error.error.data, None); + Ok(()) +} diff --git a/codex-rs/app-server/tests/suite/v2/mod.rs b/codex-rs/app-server/tests/suite/v2/mod.rs index 44f417d8b..5c40c5fc1 100644 --- a/codex-rs/app-server/tests/suite/v2/mod.rs +++ b/codex-rs/app-server/tests/suite/v2/mod.rs @@ -1,5 +1,6 @@ mod account; mod config_rpc; +mod initialize; mod model_list; mod output_schema; mod rate_limits; diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index 1a3643374..741487bab 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -8,6 +8,7 @@ use app_test_support::create_mock_responses_server_sequence_unchecked; use app_test_support::create_shell_command_sse_response; use app_test_support::format_with_current_shell_display; use app_test_support::to_response; +use codex_app_server_protocol::ClientInfo; use codex_app_server_protocol::CommandExecutionApprovalDecision; use codex_app_server_protocol::CommandExecutionRequestApprovalResponse; use codex_app_server_protocol::CommandExecutionStatus; @@ -40,6 +41,76 @@ use tempfile::TempDir; use tokio::time::timeout; const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); +const TEST_ORIGINATOR: &str = "codex_vscode"; + +#[tokio::test] +async fn turn_start_sends_originator_header() -> Result<()> { + let responses = vec![create_final_assistant_message_sse_response("Done")?]; + let server = create_mock_chat_completions_server_unchecked(responses).await; + + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), &server.uri(), "never")?; + + let mut mcp = McpProcess::new(codex_home.path()).await?; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.initialize_with_client_info(ClientInfo { + name: TEST_ORIGINATOR.to_string(), + title: Some("Codex VS Code Extension".to_string()), + version: "0.1.0".to_string(), + }), + ) + .await??; + + let thread_req = mcp + .send_thread_start_request(ThreadStartParams { + model: Some("mock-model".to_string()), + ..Default::default() + }) + .await?; + let thread_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(thread_req)), + ) + .await??; + let ThreadStartResponse { thread, .. } = to_response::(thread_resp)?; + + let turn_req = mcp + .send_turn_start_request(TurnStartParams { + thread_id: thread.id.clone(), + input: vec![V2UserInput::Text { + text: "Hello".to_string(), + }], + ..Default::default() + }) + .await?; + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(turn_req)), + ) + .await??; + + timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_notification_message("turn/completed"), + ) + .await??; + + let requests = server + .received_requests() + .await + .expect("failed to fetch received requests"); + assert!(!requests.is_empty()); + for request in requests { + let originator = request + .headers + .get("originator") + .expect("originator header missing"); + assert_eq!(originator.to_str()?, TEST_ORIGINATOR); + } + + Ok(()) +} #[tokio::test] async fn turn_start_emits_notifications_and_accepts_model_override() -> Result<()> { diff --git a/codex-rs/core/src/default_client.rs b/codex-rs/core/src/default_client.rs index 3cd882489..4ded10a3d 100644 --- a/codex-rs/core/src/default_client.rs +++ b/codex-rs/core/src/default_client.rs @@ -4,7 +4,7 @@ pub use codex_client::CodexRequestBuilder; use reqwest::header::HeaderValue; use std::sync::LazyLock; use std::sync::Mutex; -use std::sync::OnceLock; +use std::sync::RwLock; /// Set this to add a suffix to the User-Agent string. /// @@ -30,7 +30,7 @@ pub struct Originator { pub value: String, pub header_value: HeaderValue, } -static ORIGINATOR: OnceLock = OnceLock::new(); +static ORIGINATOR: LazyLock>> = LazyLock::new(|| RwLock::new(None)); #[derive(Debug)] pub enum SetOriginatorError { @@ -60,22 +60,48 @@ fn get_originator_value(provided: Option) -> Originator { } pub fn set_default_originator(value: String) -> Result<(), SetOriginatorError> { + if HeaderValue::from_str(&value).is_err() { + return Err(SetOriginatorError::InvalidHeaderValue); + } let originator = get_originator_value(Some(value)); - ORIGINATOR - .set(originator) - .map_err(|_| SetOriginatorError::AlreadyInitialized) + let Ok(mut guard) = ORIGINATOR.write() else { + return Err(SetOriginatorError::AlreadyInitialized); + }; + if guard.is_some() { + return Err(SetOriginatorError::AlreadyInitialized); + } + *guard = Some(originator); + Ok(()) } -pub fn originator() -> &'static Originator { - ORIGINATOR.get_or_init(|| get_originator_value(None)) +pub fn originator() -> Originator { + if let Ok(guard) = ORIGINATOR.read() + && let Some(originator) = guard.as_ref() + { + return originator.clone(); + } + + if std::env::var(CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR).is_ok() { + let originator = get_originator_value(None); + if let Ok(mut guard) = ORIGINATOR.write() { + match guard.as_ref() { + Some(originator) => return originator.clone(), + None => *guard = Some(originator.clone()), + } + } + return originator; + } + + get_originator_value(None) } pub fn get_codex_user_agent() -> String { let build_version = env!("CARGO_PKG_VERSION"); let os_info = os_info::get(); + let originator = originator(); let prefix = format!( "{}/{build_version} ({} {}; {}) {}", - originator().value.as_str(), + originator.value.as_str(), os_info.os_type(), os_info.version(), os_info.architecture().unwrap_or("unknown"), @@ -123,7 +149,7 @@ fn sanitize_user_agent(candidate: String, fallback: &str) -> String { tracing::warn!( "Falling back to default Codex originator because base user agent string is invalid" ); - originator().value.clone() + originator().value } } @@ -137,7 +163,7 @@ pub fn build_reqwest_client() -> reqwest::Client { use reqwest::header::HeaderMap; let mut headers = HeaderMap::new(); - headers.insert("originator", originator().header_value.clone()); + headers.insert("originator", originator().header_value); let ua = get_codex_user_agent(); let mut builder = reqwest::Client::builder() @@ -163,7 +189,7 @@ mod tests { #[test] fn test_get_codex_user_agent() { let user_agent = get_codex_user_agent(); - let originator = originator().value.as_str(); + let originator = originator().value; let prefix = format!("{originator}/"); assert!(user_agent.starts_with(&prefix)); } diff --git a/codex-rs/core/src/otel_init.rs b/codex-rs/core/src/otel_init.rs index 5ba714959..e8dd22f4c 100644 --- a/codex-rs/core/src/otel_init.rs +++ b/codex-rs/core/src/otel_init.rs @@ -15,6 +15,7 @@ use std::error::Error; pub fn build_provider( config: &Config, service_version: &str, + service_name_override: Option<&str>, ) -> Result, Box> { let to_otel_exporter = |kind: &Kind| match kind { Kind::None => OtelExporter::None, @@ -70,8 +71,11 @@ pub fn build_provider( OtelExporter::None }; + let originator = originator(); + let service_name = service_name_override.unwrap_or(originator.value.as_str()); + OtelProvider::from(&OtelSettings { - service_name: originator().value.to_owned(), + service_name: service_name.to_string(), service_version: service_version.to_string(), codex_home: config.codex_home.clone(), environment: config.otel.environment.to_string(), diff --git a/codex-rs/core/src/rollout/recorder.rs b/codex-rs/core/src/rollout/recorder.rs index d571ad191..80d95e625 100644 --- a/codex-rs/core/src/rollout/recorder.rs +++ b/codex-rs/core/src/rollout/recorder.rs @@ -143,7 +143,7 @@ impl RolloutRecorder { id: session_id, timestamp, cwd: config.cwd.clone(), - originator: originator().value.clone(), + originator: originator().value, cli_version: env!("CARGO_PKG_VERSION").to_string(), instructions, source, diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 44ce1d6e2..014257910 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -223,7 +223,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option) -> any std::process::exit(1); } - let otel = codex_core::otel_init::build_provider(&config, env!("CARGO_PKG_VERSION")); + let otel = codex_core::otel_init::build_provider(&config, env!("CARGO_PKG_VERSION"), None); #[allow(clippy::print_stderr)] let otel = match otel { diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index f4e5e771f..1680425dd 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -298,7 +298,7 @@ pub async fn run_main( ensure_oss_provider_ready(provider_id, &config).await?; } - let otel = codex_core::otel_init::build_provider(&config, env!("CARGO_PKG_VERSION")); + let otel = codex_core::otel_init::build_provider(&config, env!("CARGO_PKG_VERSION"), None); #[allow(clippy::print_stderr)] let otel = match otel { diff --git a/codex-rs/tui2/src/lib.rs b/codex-rs/tui2/src/lib.rs index ac062cf66..e5b207f5d 100644 --- a/codex-rs/tui2/src/lib.rs +++ b/codex-rs/tui2/src/lib.rs @@ -313,7 +313,7 @@ pub async fn run_main( ensure_oss_provider_ready(provider_id, &config).await?; } - let otel = codex_core::otel_init::build_provider(&config, env!("CARGO_PKG_VERSION")); + let otel = codex_core::otel_init::build_provider(&config, env!("CARGO_PKG_VERSION"), None); #[allow(clippy::print_stderr)] let otel = match otel {