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
2026-09-14 15:30:22 +00:00
committed by copyberry
parent b6a5d5bb14
commit 4d8eca1ff3
40 changed files with 355 additions and 43 deletions
@@ -97,6 +97,7 @@ pub fn build_command_execution_begin_item(payload: &ExecCommandBeginEvent) -> Th
let presentation =
CommandExecutionPresentation::from_raw(&payload.command, &payload.parsed_cmd, &payload.cwd);
ThreadItem::CommandExecution {
model_context: None,
id: payload.call_id.clone(),
plugin_id: payload.plugin_id.clone(),
script_path: payload.script_path.clone(),
@@ -123,6 +124,7 @@ pub fn build_command_execution_end_item(payload: &ExecCommandEndEvent) -> Thread
CommandExecutionPresentation::from_raw(&payload.command, &payload.parsed_cmd, &payload.cwd);
ThreadItem::CommandExecution {
model_context: None,
id: payload.call_id.clone(),
plugin_id: payload.plugin_id.clone(),
script_path: payload.script_path.clone(),
@@ -199,6 +201,7 @@ pub fn build_item_from_guardian_event(
}];
Some(ThreadItem::CommandExecution {
id: id.clone(),
model_context: assessment.model_context.clone(),
plugin_id: assessment.plugin_id.clone(),
script_path: assessment.script_path.clone(),
command,
@@ -237,6 +240,7 @@ pub fn build_item_from_guardian_event(
};
Some(ThreadItem::CommandExecution {
id: id.clone(),
model_context: assessment.model_context.clone(),
plugin_id: assessment.plugin_id.clone(),
script_path: assessment.script_path.clone(),
command,
@@ -131,6 +131,7 @@ fn guardian_stdin_reviews_preserve_parent_command_history() {
let assessment = GuardianAssessmentEvent {
review_reason: None,
model_context: None,
id: "review-stdin".into(),
target_item_id: Some("terminal-command".into()),
plugin_id: None,
@@ -2255,6 +2255,7 @@ mod tests {
.to_string(),
}];
let command_item = CoreTurnItem::CommandExecution(CoreCommandExecutionItem {
model_context: None,
id: "exec-1".to_string(),
plugin_id: Some("sample@openai-curated".to_string()),
script_path: Some("scripts/run.py".to_string()),
@@ -2340,6 +2341,7 @@ mod tests {
assert_eq!(
build_turns_from_rollout_items(&items[..2])[0].items,
vec![ThreadItem::CommandExecution {
model_context: None,
id: "exec-1".to_string(),
plugin_id: Some("sample@openai-curated".to_string()),
script_path: Some("scripts/run.py".to_string()),
@@ -2365,6 +2367,7 @@ mod tests {
assert_eq!(
turns[0].items,
vec![ThreadItem::CommandExecution {
model_context: None,
id: "exec-1".to_string(),
plugin_id: Some("sample@openai-curated".to_string()),
script_path: Some("scripts/run.py".to_string()),
@@ -3051,6 +3054,7 @@ mod tests {
assert_eq!(
turns[0].items[2],
ThreadItem::CommandExecution {
model_context: None,
id: "exec-1".into(),
plugin_id: None,
script_path: None,
@@ -3321,6 +3325,7 @@ mod tests {
assert_eq!(
turns[0].items[1],
ThreadItem::CommandExecution {
model_context: None,
id: "exec-declined".into(),
plugin_id: None,
script_path: None,
@@ -3372,6 +3377,7 @@ mod tests {
}),
EventMsg::GuardianAssessment(GuardianAssessmentEvent {
review_reason: None,
model_context: None,
id: "review-guardian-exec".into(),
target_item_id: Some("guardian-exec".into()),
plugin_id: Some("sample@openai-curated".into()),
@@ -3394,6 +3400,7 @@ mod tests {
}),
EventMsg::GuardianAssessment(GuardianAssessmentEvent {
review_reason: None,
model_context: None,
id: "review-guardian-exec".into(),
target_item_id: Some("guardian-exec".into()),
plugin_id: Some("sample@openai-curated".into()),
@@ -3428,6 +3435,7 @@ mod tests {
assert_eq!(
turns[0].items[1],
ThreadItem::CommandExecution {
model_context: None,
id: "guardian-exec".into(),
plugin_id: Some("sample@openai-curated".into()),
script_path: Some("scripts/run.py".into()),
@@ -3467,6 +3475,7 @@ mod tests {
}),
EventMsg::GuardianAssessment(GuardianAssessmentEvent {
review_reason: None,
model_context: None,
id: "review-guardian-execve".into(),
target_item_id: Some("guardian-execve".into()),
plugin_id: Some("sample@openai-curated".into()),
@@ -3500,6 +3509,7 @@ mod tests {
assert_eq!(
turns[0].items[1],
ThreadItem::CommandExecution {
model_context: None,
id: "guardian-execve".into(),
plugin_id: Some("sample@openai-curated".into()),
script_path: Some("scripts/run.py".into()),
@@ -3701,6 +3711,7 @@ mod tests {
assert_eq!(
turns[0].items[1],
ThreadItem::CommandExecution {
model_context: None,
id: "exec-late".into(),
plugin_id: None,
script_path: None,
@@ -287,6 +287,10 @@ pub enum ThreadItem {
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
CommandExecution {
#[serde(skip)]
#[schemars(skip)]
#[ts(skip)]
model_context: Option<codex_protocol::items::ModelInvocationContext>,
id: String,
/// Trusted first-party plugin id when this command resolves to one plugin script.
#[serde(default)]
@@ -910,6 +914,7 @@ impl From<CoreTurnItem> for ThreadItem {
);
ThreadItem::CommandExecution {
id: command.id,
model_context: command.model_context,
plugin_id: command.plugin_id,
script_path: command.script_path,
command: presentation.command,
@@ -3210,6 +3210,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
);
let command_item = TurnItem::CommandExecution(CommandExecutionItem {
model_context: None,
id: "exec-1".to_string(),
plugin_id: Some("sample@openai-curated".to_string()),
script_path: Some("scripts/run.py".to_string()),
@@ -3242,6 +3243,7 @@ fn core_turn_item_into_thread_item_converts_supported_variants() {
assert_eq!(
ThreadItem::from(command_item),
ThreadItem::CommandExecution {
model_context: None,
id: "exec-1".to_string(),
plugin_id: Some("sample@openai-curated".to_string()),
script_path: Some("scripts/run.py".to_string()),