Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion codex-rs/exec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ use codex_core::default_client::set_default_originator;
use codex_core::find_thread_path_by_id_str;
use codex_core::find_thread_path_by_name_str;

const DEFAULT_ANALYTICS_ENABLED: bool = true;

enum InitialOperation {
UserTurn {
items: Vec<UserInput>,
Expand Down Expand Up @@ -316,7 +318,12 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
}

let otel = match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
codex_core::otel_init::build_provider(&config, env!("CARGO_PKG_VERSION"), None, false)
codex_core::otel_init::build_provider(
&config,
env!("CARGO_PKG_VERSION"),
None,
DEFAULT_ANALYTICS_ENABLED,
)
})) {
Ok(Ok(otel)) => otel,
Ok(Err(e)) => {
Expand Down Expand Up @@ -927,6 +934,11 @@ mod tests {
use super::*;
use pretty_assertions::assert_eq;

#[test]
fn exec_defaults_analytics_to_enabled() {
assert_eq!(DEFAULT_ANALYTICS_ENABLED, true);
}

#[test]
fn builds_uncommitted_review_request() {
let request = build_review_request(ReviewArgs {
Expand Down
111 changes: 92 additions & 19 deletions codex-rs/mcp-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use tracing::debug;
use tracing::error;
use tracing::info;
use tracing_subscriber::EnvFilter;
use tracing_subscriber::prelude::*;

mod codex_tool_config;
mod codex_tool_runner;
Expand All @@ -45,19 +46,53 @@ pub use crate::patch_approval::PatchApprovalResponse;
/// is a balance between throughput and memory usage – 128 messages should be
/// plenty for an interactive CLI.
const CHANNEL_CAPACITY: usize = 128;
const DEFAULT_ANALYTICS_ENABLED: bool = true;
const OTEL_SERVICE_NAME: &str = "codex_mcp_server";

type IncomingMessage = JsonRpcMessage<ClientRequest, Value, ClientNotification>;

pub async fn run_main(
arg0_paths: Arg0DispatchPaths,
cli_config_overrides: CliConfigOverrides,
) -> IoResult<()> {
// Install a simple subscriber so `tracing` output is visible. Users can
// control the log level with `RUST_LOG`.
tracing_subscriber::fmt()
// Parse CLI overrides once and derive the base Config eagerly so later
// components do not need to work with raw TOML values.
let cli_kv_overrides = cli_config_overrides.parse_overrides().map_err(|e| {
std::io::Error::new(
ErrorKind::InvalidInput,
format!("error parsing -c overrides: {e}"),
)
})?;
let config = Config::load_with_cli_overrides(cli_kv_overrides)
.await
.map_err(|e| {
std::io::Error::new(ErrorKind::InvalidData, format!("error loading config: {e}"))
})?;

let otel = codex_core::otel_init::build_provider(
&config,
env!("CARGO_PKG_VERSION"),
Some(OTEL_SERVICE_NAME),
DEFAULT_ANALYTICS_ENABLED,
)
.map_err(|e| {
std::io::Error::new(
ErrorKind::InvalidData,
format!("error loading otel config: {e}"),
)
})?;

let fmt_layer = tracing_subscriber::fmt::layer()
.with_writer(std::io::stderr)
.with_env_filter(EnvFilter::from_default_env())
.init();
.with_filter(EnvFilter::from_default_env());
let otel_logger_layer = otel.as_ref().and_then(|provider| provider.logger_layer());
let otel_tracing_layer = otel.as_ref().and_then(|provider| provider.tracing_layer());

let _ = tracing_subscriber::registry()
.with(fmt_layer)
.with(otel_logger_layer)
.with(otel_tracing_layer)
.try_init();

// Set up channels.
let (incoming_tx, mut incoming_rx) = mpsc::channel::<IncomingMessage>(CHANNEL_CAPACITY);
Expand Down Expand Up @@ -86,20 +121,6 @@ pub async fn run_main(
}
});

// Parse CLI overrides once and derive the base Config eagerly so later
// components do not need to work with raw TOML values.
let cli_kv_overrides = cli_config_overrides.parse_overrides().map_err(|e| {
std::io::Error::new(
ErrorKind::InvalidInput,
format!("error parsing -c overrides: {e}"),
)
})?;
let config = Config::load_with_cli_overrides(cli_kv_overrides)
.await
.map_err(|e| {
std::io::Error::new(ErrorKind::InvalidData, format!("error loading config: {e}"))
})?;

// Task: process incoming messages.
let processor_handle = tokio::spawn({
let outgoing_message_sender = OutgoingMessageSender::new(outgoing_tx);
Expand Down Expand Up @@ -152,3 +173,55 @@ pub async fn run_main(

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use codex_core::config::ConfigBuilder;
use codex_core::config::types::OtelExporterKind;
use pretty_assertions::assert_eq;
use std::collections::HashMap;
use tempfile::TempDir;

#[test]
fn mcp_server_defaults_analytics_to_enabled() {
assert_eq!(DEFAULT_ANALYTICS_ENABLED, true);
}

#[tokio::test]
async fn mcp_server_builds_otel_provider_with_logs_traces_and_metrics() -> anyhow::Result<()> {
let codex_home = TempDir::new()?;
let mut config = ConfigBuilder::default()
.codex_home(codex_home.path().to_path_buf())
.build()
.await?;
let exporter = OtelExporterKind::OtlpGrpc {
endpoint: "http://localhost:4317".to_string(),
headers: HashMap::new(),
tls: None,
};
config.otel.exporter = exporter.clone();
config.otel.trace_exporter = exporter.clone();
config.otel.metrics_exporter = exporter;
config.analytics_enabled = None;

let provider = codex_core::otel_init::build_provider(
&config,
"0.0.0-test",
Some(OTEL_SERVICE_NAME),
DEFAULT_ANALYTICS_ENABLED,
)
.map_err(|err| anyhow::anyhow!(err.to_string()))?
.expect("otel provider");

assert!(provider.logger.is_some(), "expected log exporter");
assert!(
provider.tracer_provider.is_some(),
"expected trace exporter"
);
assert!(provider.metrics().is_some(), "expected metrics exporter");
provider.shutdown();

Ok(())
}
}
Loading