[codex-analytics] add session source to client metadata (#17374)

## Summary

Adds `thread_source` field to the existing Codex turn metadata sent to
Responses API
- Sends `thread_source: "user"` for user-initiated sessions: CLI, VS
Code, and Exec
- Sends `thread_source: "subagent"` for subagent sessions
- Omits `thread_source` for MCP, custom, and unknown session sources
- Uses the existing turn metadata transport:
  - HTTP requests send through the `x-codex-turn-metadata` header
- WebSocket `response.create` requests send through
`client_metadata["x-codex-turn-metadata"]`

## Testing
- `cargo test -p codex-protocol
session_source_thread_source_name_classifies_user_and_subagent_sources`
- `cargo test -p codex-core turn_metadata_state`
- `cargo test -p codex-core --test responses_headers
responses_stream_includes_turn_metadata_header_for_git_workspace_e2e --
--nocapture`
This commit is contained in:
marksteinbrick-oai
2026-04-14 08:55:12 -07:00
committed by GitHub
parent f030ab62eb
commit 61fe23159e
8 changed files with 94 additions and 13 deletions

View File

@@ -2633,6 +2633,15 @@ impl SessionSource {
})
}
/// Low cardinality thread source label for analytics.
pub fn thread_source_name(&self) -> Option<&'static str> {
match self {
SessionSource::Cli | SessionSource::VSCode | SessionSource::Exec => Some("user"),
SessionSource::SubAgent(_) => Some("subagent"),
SessionSource::Mcp | SessionSource::Custom(_) | SessionSource::Unknown => None,
}
}
pub fn get_nickname(&self) -> Option<String> {
match self {
SessionSource::SubAgent(SubAgentSource::ThreadSpawn { agent_nickname, .. }) => {
@@ -3882,6 +3891,24 @@ mod tests {
);
}
#[test]
fn session_source_thread_source_name_classifies_user_and_subagent_sources() {
for (source, expected) in [
(SessionSource::Cli, Some("user")),
(SessionSource::VSCode, Some("user")),
(SessionSource::Exec, Some("user")),
(
SessionSource::SubAgent(SubAgentSource::Review),
Some("subagent"),
),
(SessionSource::Mcp, None),
(SessionSource::Custom("atlas".to_string()), None),
(SessionSource::Unknown, None),
] {
assert_eq!(source.thread_source_name(), expected);
}
}
#[test]
fn session_source_restriction_product_defaults_non_subagent_sources_to_codex() {
assert_eq!(