add session source to telemetry

This commit is contained in:
Roy Han
2026-03-19 17:56:55 -07:00
parent 659d21f146
commit 678f52a33b
33 changed files with 1264 additions and 83 deletions
+260
View File
@@ -17,6 +17,9 @@ use crate::tools::format_exec_output_str;
use codex_protocol::ThreadId;
use codex_protocol::models::FunctionCallOutputBody;
use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::LocalShellAction;
use codex_protocol::models::LocalShellExecAction;
use codex_protocol::models::LocalShellStatus;
use codex_protocol::permissions::FileSystemAccessMode;
use codex_protocol::permissions::FileSystemPath;
use codex_protocol::permissions::FileSystemSandboxEntry;
@@ -61,6 +64,7 @@ use codex_app_server_protocol::AppInfo;
use codex_execpolicy::Decision;
use codex_execpolicy::NetworkRuleProtocol;
use codex_execpolicy::Policy;
use codex_git::GhostCommit;
use codex_network_proxy::NetworkProxyConfig;
use codex_otel::TelemetryAuthMode;
use codex_protocol::models::BaseInstructions;
@@ -68,6 +72,7 @@ use codex_protocol::models::ContentItem;
use codex_protocol::models::DeveloperInstructions;
use codex_protocol::models::ResponseInputItem;
use codex_protocol::models::ResponseItem;
use codex_protocol::models::WebSearchAction;
use codex_protocol::openai_models::ModelsResponse;
use codex_protocol::protocol::ConversationAudioParams;
use codex_protocol::protocol::RealtimeAudioFrame;
@@ -3679,6 +3684,7 @@ async fn build_initial_context_omits_default_image_save_location_with_image_hist
status: "completed".to_string(),
revised_prompt: Some("a tiny blue square".to_string()),
result: "Zm9v".to_string(),
metadata: None,
}],
None,
)
@@ -3722,6 +3728,7 @@ async fn handle_output_item_done_records_image_save_history_message() {
status: "completed".to_string(),
revised_prompt: Some("a tiny blue square".to_string()),
result: "Zm9v".to_string(),
metadata: None,
};
let mut ctx = HandleOutputCtx {
@@ -3762,6 +3769,7 @@ async fn handle_output_item_done_skips_image_save_message_when_save_fails() {
status: "completed".to_string(),
revised_prompt: Some("broken payload".to_string()),
result: "_-8".to_string(),
metadata: None,
};
let mut ctx = HandleOutputCtx {
@@ -4355,6 +4363,8 @@ async fn task_finish_emits_prompt_queued_metadata_for_injected_user_input_when_f
..
} if metadata.user_message_type
== Some(codex_protocol::models::UserMessageType::PromptQueued)
&& metadata.session_source
== Some(codex_protocol::models::SessionSourceMetadata::Exec)
)
));
@@ -4389,6 +4399,203 @@ async fn task_finish_emits_prompt_queued_metadata_for_injected_user_input_when_f
));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn record_response_item_stamps_session_source_on_all_supported_variants() {
let (mut sess, tc, _rx) = make_session_and_context_with_rx().await;
Arc::get_mut(&mut sess)
.expect("session should be uniquely owned in this test")
.features
.enable(crate::features::Feature::ItemMetadata)
.expect("feature flag should be enabled for this test");
sess.spawn_task(
Arc::clone(&tc),
vec![UserInput::Text {
text: "start".to_string(),
text_elements: Vec::new(),
}],
NeverEndingTask {
kind: TaskKind::Regular,
listen_to_cancellation_token: false,
},
)
.await;
let items = vec![
ResponseItem::Message {
id: None,
role: "assistant".to_string(),
content: vec![ContentItem::OutputText {
text: "hello".to_string(),
}],
metadata: None,
end_turn: None,
phase: None,
},
ResponseItem::Reasoning {
id: "reasoning-1".to_string(),
summary: vec![],
content: None,
encrypted_content: None,
metadata: None,
},
ResponseItem::LocalShellCall {
id: None,
call_id: Some("shell-1".to_string()),
status: LocalShellStatus::Completed,
action: LocalShellAction::Exec(LocalShellExecAction {
command: vec!["echo".to_string(), "hello".to_string()],
timeout_ms: None,
working_directory: None,
env: None,
user: None,
}),
metadata: None,
},
ResponseItem::FunctionCall {
id: None,
name: "shell".to_string(),
namespace: None,
arguments: "{}".to_string(),
call_id: "call-1".to_string(),
metadata: None,
},
ResponseItem::ToolSearchCall {
id: None,
call_id: Some("search-1".to_string()),
status: None,
execution: "client".to_string(),
arguments: serde_json::json!({"query": "calendar"}),
metadata: None,
},
ResponseItem::FunctionCallOutput {
call_id: "call-output-1".to_string(),
output: FunctionCallOutputPayload {
body: FunctionCallOutputBody::Text("ok".to_string()),
success: None,
},
metadata: None,
},
ResponseItem::CustomToolCall {
id: None,
status: Some("completed".to_string()),
call_id: "custom-1".to_string(),
name: "custom".to_string(),
input: "payload".to_string(),
metadata: None,
},
ResponseItem::CustomToolCallOutput {
call_id: "custom-output-1".to_string(),
output: FunctionCallOutputPayload {
body: FunctionCallOutputBody::Text("done".to_string()),
success: None,
},
metadata: None,
},
ResponseItem::ToolSearchOutput {
call_id: Some("search-out-1".to_string()),
status: "completed".to_string(),
execution: "client".to_string(),
tools: vec![],
metadata: None,
},
ResponseItem::WebSearchCall {
id: None,
status: Some("completed".to_string()),
action: Some(WebSearchAction::Search {
query: Some("weather".to_string()),
queries: None,
}),
metadata: None,
},
ResponseItem::ImageGenerationCall {
id: "image-1".to_string(),
status: "completed".to_string(),
revised_prompt: Some("cat".to_string()),
result: "image-result".to_string(),
metadata: None,
},
ResponseItem::GhostSnapshot {
ghost_commit: GhostCommit::new("deadbeef".to_string(), None, Vec::new(), Vec::new()),
metadata: None,
},
ResponseItem::Compaction {
encrypted_content: "blob".to_string(),
metadata: None,
},
];
for item in items {
sess.record_response_item_and_emit_turn_item(tc.as_ref(), item)
.await;
}
let expected_session_source = codex_protocol::models::SessionSourceMetadata::Exec;
let stamped_count = sess
.clone_history()
.await
.raw_items()
.iter()
.filter(|item| match item {
ResponseItem::Message {
metadata: Some(metadata),
..
}
| ResponseItem::Reasoning {
metadata: Some(metadata),
..
}
| ResponseItem::LocalShellCall {
metadata: Some(metadata),
..
}
| ResponseItem::FunctionCall {
metadata: Some(metadata),
..
}
| ResponseItem::ToolSearchCall {
metadata: Some(metadata),
..
}
| ResponseItem::FunctionCallOutput {
metadata: Some(metadata),
..
}
| ResponseItem::CustomToolCall {
metadata: Some(metadata),
..
}
| ResponseItem::CustomToolCallOutput {
metadata: Some(metadata),
..
}
| ResponseItem::ToolSearchOutput {
metadata: Some(metadata),
..
}
| ResponseItem::WebSearchCall {
metadata: Some(metadata),
..
}
| ResponseItem::ImageGenerationCall {
metadata: Some(metadata),
..
}
| ResponseItem::GhostSnapshot {
metadata: Some(metadata),
..
}
| ResponseItem::Compaction {
metadata: Some(metadata),
..
} => metadata.session_source == Some(expected_session_source.clone()),
_ => false,
})
.count();
assert_eq!(stamped_count, 13);
}
#[test]
fn review_decision_metadata_mapping_is_stable() {
assert_eq!(
@@ -4454,6 +4661,59 @@ fn sandbox_policy_metadata_mapping_is_stable() {
);
}
#[test]
fn session_source_metadata_mapping_is_stable() {
assert_eq!(
session_source_to_metadata(&SessionSource::Cli),
codex_protocol::models::SessionSourceMetadata::Cli
);
assert_eq!(
session_source_to_metadata(&SessionSource::VSCode),
codex_protocol::models::SessionSourceMetadata::Vscode
);
assert_eq!(
session_source_to_metadata(&SessionSource::Exec),
codex_protocol::models::SessionSourceMetadata::Exec
);
assert_eq!(
session_source_to_metadata(&SessionSource::Mcp),
codex_protocol::models::SessionSourceMetadata::Mcp
);
assert_eq!(
session_source_to_metadata(&SessionSource::SubAgent(SubAgentSource::Review)),
codex_protocol::models::SessionSourceMetadata::SubagentReview
);
assert_eq!(
session_source_to_metadata(&SessionSource::SubAgent(SubAgentSource::Compact)),
codex_protocol::models::SessionSourceMetadata::SubagentCompact
);
assert_eq!(
session_source_to_metadata(&SessionSource::SubAgent(
SubAgentSource::MemoryConsolidation
)),
codex_protocol::models::SessionSourceMetadata::SubagentMemoryConsolidation
);
assert_eq!(
session_source_to_metadata(&SessionSource::SubAgent(SubAgentSource::ThreadSpawn {
parent_thread_id: ThreadId::default(),
depth: 2,
agent_nickname: Some("mini".to_string()),
agent_role: Some("reviewer".to_string()),
})),
codex_protocol::models::SessionSourceMetadata::SubagentThreadSpawn
);
assert_eq!(
session_source_to_metadata(&SessionSource::SubAgent(SubAgentSource::Other(
"custom".to_string()
))),
codex_protocol::models::SessionSourceMetadata::SubagentOther
);
assert_eq!(
session_source_to_metadata(&SessionSource::Unknown),
codex_protocol::models::SessionSourceMetadata::Unknown
);
}
async fn setup_tool_call_metadata_runtime_test() -> (
Arc<Session>,
Arc<TurnContext>,