Attribute command and plugin analytics to the invoking model (#45445)

## Why

Command execution and plugin measurement events lack model and reasoning-effort labels. Attribution needs to reflect the step that invoked the command, even when model settings change before a background process finishes.

## What changed

- Add `model_slug` and `reasoning_effort` to command execution and plugin measurement analytics.
- Capture model context from resolved step settings and carry it through execution, approval, Guardian review, and plugin metrics collection.
- Preserve the first command-start model context when subsequent start notifications arrive.
- Keep the carried context out of serialized protocol items and generated schemas.

## Testing

Extend analytics tests to cover model switches before invocation and during background execution, default reasoning effort, Guardian-denied commands, and repeated start notifications retaining the original model context.

GitOrigin-RevId: af90e1c0d39bab625f2e89786085b61a9b96c0ce
This commit is contained in:
jwang-openai authored and copyberry committed 2026-09-14 15:30:22 +00:00
1 parent b6a5d5bb14
commit 4d8eca1ff3
40 files changed
+355 -43

No files matched your search

@@ -52,6 +52,8 @@ async fn command_approval_holds_an_elicitation_until_response() {
.request_command_approval(
turn_context.as_ref(),
ExecApprovalKind::Command,
crate::guardian::GuardianReviewContext::from(turn_context.clone())
.model_context(),
"call-1".to_string(),
/*approval_id*/ None,
/*environment_id*/ None,
+4 -1
View File
@@ -1,4 +1,3 @@
use crate::context::GuardianContextMode;
use std::borrow::Cow;
use std::collections::HashMap;
use std::collections::HashSet;
@@ -23,6 +22,7 @@ use crate::config::ManagedFeatures;
use crate::config::resolve_tool_suggest_config_from_layer_stack;
use crate::context::ContextualUserFragment;
use crate::context::DeveloperInstructions;
use crate::context::GuardianContextMode;
use crate::context::GuardianPolicy;
use crate::context::ManagedDeveloperInstructions;
use crate::context::ModelSwitchInstructions;
@@ -107,6 +107,7 @@ use codex_protocol::config_types::WebSearchMode;
use codex_protocol::dynamic_tools::DynamicToolResponse;
use codex_protocol::dynamic_tools::DynamicToolSpec;
use codex_protocol::items::EnteredReviewModeItem;
use codex_protocol::items::ModelInvocationContext;
use codex_protocol::items::SubAgentActivityItem;
use codex_protocol::items::TurnItem;
use codex_protocol::items::UserMessageItem;
@@ -2790,6 +2791,7 @@ impl Session {
&self,
turn_context: &TurnContext,
kind: ExecApprovalKind,
model_context: ModelInvocationContext,
call_id: String,
approval_id: Option<String>,
environment_id: Option<String>,
@@ -2853,6 +2855,7 @@ impl Session {
.map(PluginCommandAttribution::serialized_fields)
.unzip();
let event = EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent {
model_context: Some(model_context),
kind,
call_id,
plugin_id,
+11
View File
@@ -12,6 +12,7 @@ use codex_exec_server::ExecutorCapabilityDiscoverySnapshot;
use codex_exec_server::ResolvedSelectedCapabilityRoot;
use codex_mcp::McpBinding;
use codex_otel::SessionTelemetry;
use codex_protocol::items::ModelInvocationContext;
use codex_protocol::protocol::TurnContextItem;
/// Request-scoped state that may change between model sampling requests.
@@ -43,4 +44,14 @@ impl StepContext {
item.summary = self.settings.reasoning_summary;
item
}
pub(crate) fn model_context(&self) -> ModelInvocationContext {
ModelInvocationContext {
model_slug: self.settings.model_info.slug.clone(),
reasoning_effort: self
.settings
.effective_reasoning_effort()
.map(|effort| effort.to_string()),
}
}
}