From 4123bf61891636772eeed9a2387a6413645270d1 Mon Sep 17 00:00:00 2001 From: stevenlee-oai Date: Thu, 23 Jul 2026 23:50:13 +0000 Subject: [PATCH] Track app/read request duration (#35048) ## What changed - Record `codex.apps.read.duration_ms` when `app/read` returns a response. - Tag the duration by the request's `include_tools` value. - Move the `app/read` handler into its own request-processor module. GitOrigin-RevId: e1563cd41c72ad03e7f5d5d33cd8a3b35e27984b --- .../src/request_processors/apps_processor.rs | 80 +--------------- .../request_processors/apps_processor/read.rs | 91 +++++++++++++++++++ 2 files changed, 94 insertions(+), 77 deletions(-) create mode 100644 codex-rs/app-server/src/request_processors/apps_processor/read.rs diff --git a/codex-rs/app-server/src/request_processors/apps_processor.rs b/codex-rs/app-server/src/request_processors/apps_processor.rs index 3dc17ae0c7..ea942e7eca 100644 --- a/codex-rs/app-server/src/request_processors/apps_processor.rs +++ b/codex-rs/app-server/src/request_processors/apps_processor.rs @@ -1,8 +1,10 @@ use super::*; use crate::app_info::app_info_to_api; -use crate::app_info::connector_metadata_to_api; mod installed; +mod read; + +pub(super) use read::APP_READ_MAX_IDS; pub(crate) struct AppsRequestProcessor { auth_manager: Arc, @@ -35,80 +37,6 @@ impl AppsRequestProcessor { } } - pub(crate) async fn apps_read( - &self, - params: AppsReadParams, - ) -> Result, JSONRPCErrorError> { - let AppsReadParams { - app_ids, - include_tools, - } = params; - if app_ids.len() > APP_READ_MAX_IDS { - return Err(invalid_params(format!( - "app/read accepts at most {APP_READ_MAX_IDS} appIds" - ))); - } - - let mut seen_app_ids = HashSet::new(); - let app_ids = app_ids - .into_iter() - .filter(|app_id| seen_app_ids.insert(app_id.clone())) - .collect::>(); - let config = self.load_latest_config(/*fallback_cwd*/ None).await?; - let auth = self.auth_manager.auth().await; - if !config - .features - .apps_enabled_for_auth(auth.as_ref().is_some_and(CodexAuth::uses_codex_backend)) - || !self - .workspace_codex_plugins_enabled(&config, auth.as_ref()) - .await - { - return Ok(Some( - AppsReadResponse { - apps: Vec::new(), - missing_app_ids: app_ids, - } - .into(), - )); - } - let auth = auth - .as_ref() - .ok_or_else(|| internal_error("app/read requires ChatGPT auth".to_string()))?; - - let connectors::ConnectorMetadataReadResult { - apps, - missing_app_ids, - } = connectors::read_connector_metadata(&config, auth, &app_ids, include_tools) - .await - .map_err(|err| internal_error(format!("failed to read app metadata: {err}")))?; - let loaded_plugins = self - .thread_manager - .plugins_manager() - .plugins_for_config(&config.plugins_config_input()) - .await; - let connector_snapshot = - codex_connectors::ConnectorSnapshot::from_plugin_capability_summaries( - loaded_plugins.capability_summaries(), - ); - let apps = apps - .into_iter() - .map(|metadata| { - let mut app = connector_metadata_to_api(metadata); - app.plugin_display_names = connector_snapshot - .plugin_display_names_for_connector_id(app.id.as_str()) - .to_vec(); - app - }) - .collect(); - Ok(Some( - AppsReadResponse { - apps, - missing_app_ids, - } - .into(), - )) - } - pub(crate) async fn apps_list( &self, request_id: &ConnectionRequestId, @@ -477,8 +405,6 @@ fn record_legacy_apps_installed_duration(started_at: Instant, reload: bool) { ); } } -pub(super) const APP_READ_MAX_IDS: usize = 100; - enum AppListLoadResult { Accessible(Result), Directory(Result, String>), diff --git a/codex-rs/app-server/src/request_processors/apps_processor/read.rs b/codex-rs/app-server/src/request_processors/apps_processor/read.rs new file mode 100644 index 0000000000..0878747ba3 --- /dev/null +++ b/codex-rs/app-server/src/request_processors/apps_processor/read.rs @@ -0,0 +1,91 @@ +use super::*; +use crate::app_info::connector_metadata_to_api; + +pub(in crate::request_processors) const APP_READ_MAX_IDS: usize = 100; +const APPS_READ_DURATION_METRIC: &str = "codex.apps.read.duration_ms"; + +impl AppsRequestProcessor { + pub(crate) async fn apps_read( + &self, + params: AppsReadParams, + ) -> Result, JSONRPCErrorError> { + let started_at = Instant::now(); + let AppsReadParams { + app_ids, + include_tools, + } = params; + if app_ids.len() > APP_READ_MAX_IDS { + return Err(invalid_params(format!( + "app/read accepts at most {APP_READ_MAX_IDS} appIds" + ))); + } + + let mut seen_app_ids = HashSet::new(); + let app_ids = app_ids + .into_iter() + .filter(|app_id| seen_app_ids.insert(app_id.clone())) + .collect::>(); + let config = self.load_latest_config(/*fallback_cwd*/ None).await?; + let auth = self.auth_manager.auth().await; + if !config + .features + .apps_enabled_for_auth(auth.as_ref().is_some_and(CodexAuth::uses_codex_backend)) + || !self + .workspace_codex_plugins_enabled(&config, auth.as_ref()) + .await + { + let response = AppsReadResponse { + apps: Vec::new(), + missing_app_ids: app_ids, + }; + record_apps_read_duration(started_at, include_tools); + return Ok(Some(response.into())); + } + let auth = auth + .as_ref() + .ok_or_else(|| internal_error("app/read requires ChatGPT auth".to_string()))?; + + let connectors::ConnectorMetadataReadResult { + apps, + missing_app_ids, + } = connectors::read_connector_metadata(&config, auth, &app_ids, include_tools) + .await + .map_err(|err| internal_error(format!("failed to read app metadata: {err}")))?; + let loaded_plugins = self + .thread_manager + .plugins_manager() + .plugins_for_config(&config.plugins_config_input()) + .await; + let connector_snapshot = + codex_connectors::ConnectorSnapshot::from_plugin_capability_summaries( + loaded_plugins.capability_summaries(), + ); + let apps = apps + .into_iter() + .map(|metadata| { + let mut app = connector_metadata_to_api(metadata); + app.plugin_display_names = connector_snapshot + .plugin_display_names_for_connector_id(app.id.as_str()) + .to_vec(); + app + }) + .collect(); + let response = AppsReadResponse { + apps, + missing_app_ids, + }; + record_apps_read_duration(started_at, include_tools); + Ok(Some(response.into())) + } +} + +fn record_apps_read_duration(started_at: Instant, include_tools: bool) { + let include_tools = if include_tools { "true" } else { "false" }; + if let Some(metrics) = codex_otel::global() { + let _ = metrics.record_duration( + APPS_READ_DURATION_METRIC, + started_at.elapsed(), + &[("include_tools", include_tools)], + ); + } +}