diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 79b2d4451..f297d161e 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -2812,10 +2812,11 @@ pub(crate) async fn run_turn( .await, ); + let otel_manager = turn_context.client.get_otel_manager(); let SkillInjections { items: skill_items, warnings: skill_warnings, - } = build_skill_injections(&input, skills_outcome.as_ref()).await; + } = build_skill_injections(&input, skills_outcome.as_ref(), Some(&otel_manager)).await; for message in skill_warnings { sess.send_event(&turn_context, EventMsg::Warning(WarningEvent { message })) diff --git a/codex-rs/core/src/skills/injection.rs b/codex-rs/core/src/skills/injection.rs index ffe879bcd..9aa12d775 100644 --- a/codex-rs/core/src/skills/injection.rs +++ b/codex-rs/core/src/skills/injection.rs @@ -4,6 +4,7 @@ use std::path::PathBuf; use crate::instructions::SkillInstructions; use crate::skills::SkillLoadOutcome; use crate::skills::SkillMetadata; +use codex_otel::OtelManager; use codex_protocol::models::ResponseItem; use codex_protocol::user_input::UserInput; use tokio::fs; @@ -17,6 +18,7 @@ pub(crate) struct SkillInjections { pub(crate) async fn build_skill_injections( inputs: &[UserInput], skills: Option<&SkillLoadOutcome>, + otel: Option<&OtelManager>, ) -> SkillInjections { if inputs.is_empty() { return SkillInjections::default(); @@ -40,6 +42,7 @@ pub(crate) async fn build_skill_injections( for skill in mentioned_skills { match fs::read_to_string(&skill.path).await { Ok(contents) => { + emit_skill_injected_metric(otel, &skill, "ok"); result.items.push(ResponseItem::from(SkillInstructions { name: skill.name, path: skill.path.to_string_lossy().into_owned(), @@ -47,6 +50,7 @@ pub(crate) async fn build_skill_injections( })); } Err(err) => { + emit_skill_injected_metric(otel, &skill, "error"); let message = format!( "Failed to load skill {name} at {path}: {err:#}", name = skill.name, @@ -60,6 +64,18 @@ pub(crate) async fn build_skill_injections( result } +fn emit_skill_injected_metric(otel: Option<&OtelManager>, skill: &SkillMetadata, status: &str) { + let Some(otel) = otel else { + return; + }; + + otel.counter( + "codex.skill.injected", + 1, + &[("status", status), ("skill", skill.name.as_str())], + ); +} + fn collect_explicit_skill_mentions( inputs: &[UserInput], skills: &[SkillMetadata],