1use std::time::Instant;
2
3use crate::facts::AcceptedLineFingerprint;
4use crate::facts::AppInvocation;
5use crate::facts::CodexCompactionEvent;
6use crate::facts::CodexErrKind;
7use crate::facts::CodexGoalEvent;
8use crate::facts::CompactionImplementation;
9use crate::facts::CompactionPhase;
10use crate::facts::CompactionReason;
11use crate::facts::CompactionStatus;
12use crate::facts::CompactionStrategy;
13use crate::facts::CompactionTrigger;
14use crate::facts::GoalEventKind;
15use crate::facts::HookRunFact;
16use crate::facts::InvocationType;
17use crate::facts::PluginInstallRequested;
18use crate::facts::PluginState;
19use crate::facts::SubAgentThreadStartedInput;
20use crate::facts::ThreadInitializationMode;
21use crate::facts::TrackEventsContext;
22use crate::facts::TurnStatus;
23use crate::facts::TurnSteerRejectionReason;
24use crate::facts::TurnSteerResult;
25use crate::facts::TurnSubmissionType;
26use crate::now_unix_millis;
27use codex_app_server_protocol::CodexErrorInfo;
28use codex_app_server_protocol::CommandExecutionSource;
29use codex_login::default_client::originator;
30use codex_plugin::PluginId;
31use codex_plugin::PluginTelemetryMetadata;
32use codex_protocol::approvals::NetworkApprovalProtocol;
33use codex_protocol::models::AdditionalPermissionProfile;
34use codex_protocol::models::SandboxPermissions;
35use codex_protocol::protocol::GuardianAssessmentOutcome;
36use codex_protocol::protocol::GuardianCommandSource;
37use codex_protocol::protocol::GuardianRiskLevel;
38use codex_protocol::protocol::GuardianUserAuthorization;
39use codex_protocol::protocol::HookEventName;
40use codex_protocol::protocol::HookRunStatus;
41use codex_protocol::protocol::HookSource;
42use codex_protocol::protocol::SubAgentSource;
43use codex_protocol::protocol::ThreadSource;
44use codex_protocol::protocol::TokenUsage;
45use serde::Serialize;
46
47#[derive(Clone, Copy, Debug, Serialize)]
48#[serde(rename_all = "snake_case")]
49pub enum AppServerRpcTransport {
50 Stdio,
51 Websocket,
52 InProcess,
53}
54
55#[derive(Serialize)]
56pub(crate) struct TrackEventsRequest {
57 pub(crate) events: Vec<TrackEventRequest>,
58}
59
60#[derive(Serialize)]
61#[serde(untagged)]
62pub(crate) enum TrackEventRequest {
63 SkillInvocation(SkillInvocationEventRequest),
64 ThreadInitialized(ThreadInitializedEvent),
65 GuardianReview(Box<GuardianReviewEventRequest>),
66 AppMentioned(CodexAppMentionedEventRequest),
67 AppUsed(CodexAppUsedEventRequest),
68 HookRun(CodexHookRunEventRequest),
69 Compaction(Box<CodexCompactionEventRequest>),
70 Goal(Box<CodexGoalEventRequest>),
71 TurnEvent(Box<CodexTurnEventRequest>),
72 TurnSteer(CodexTurnSteerEventRequest),
73 CommandExecution(CodexCommandExecutionEventRequest),
74 FileChange(CodexFileChangeEventRequest),
75 McpToolCall(CodexMcpToolCallEventRequest),
76 DynamicToolCall(CodexDynamicToolCallEventRequest),
77 CollabAgentToolCall(CodexCollabAgentToolCallEventRequest),
78 WebSearch(CodexWebSearchEventRequest),
79 ImageGeneration(CodexImageGenerationEventRequest),
80 AcceptedLineFingerprints(Box<CodexAcceptedLineFingerprintsEventRequest>),
81 #[allow(dead_code)]
82 ReviewEvent(CodexReviewEventRequest),
83 PluginUsed(CodexPluginUsedEventRequest),
84 PluginInstallRequested(CodexPluginInstallRequestedEventRequest),
85 PluginInstalled(CodexPluginEventRequest),
86 PluginUninstalled(CodexPluginEventRequest),
87 PluginEnabled(CodexPluginEventRequest),
88 PluginDisabled(CodexPluginEventRequest),
89 PluginInstallFailed(CodexPluginInstallFailedEventRequest),
90 ExternalAgentConfigImportCompleted(CodexOnboardingExternalAgentImportCompleteEventRequest),
91 ExternalAgentConfigImportFailure(CodexOnboardingExternalAgentImportFailureEventRequest),
92}
93
94impl TrackEventRequest {
95 pub(crate) fn should_send_in_isolated_request(&self) -> bool {
96 matches!(self, Self::AcceptedLineFingerprints(_))
97 }
98
99 pub(crate) fn can_send_with_api_key_auth(&self) -> bool {
100 match self {
101 Self::PluginUsed(event) => event.event_params.plugin.plugin_id.is_some(),
102 Self::SkillInvocation(event) => event.event_params.plugin_id.is_some(),
103 Self::McpToolCall(event) => event.event_params.plugin_id.is_some(),
104 _ => false,
105 }
106 }
107}
108
109#[derive(Serialize)]
110pub(crate) struct CodexAcceptedLineFingerprintsEventParams {
111 pub(crate) event_type: &'static str,
112 pub(crate) turn_id: String,
113 pub(crate) thread_id: String,
114 pub(crate) product_surface: Option<String>,
115 pub(crate) model_slug: Option<String>,
116 pub(crate) completed_at: u64,
117 pub(crate) repo_hash: Option<String>,
118 pub(crate) accepted_added_lines: u64,
119 pub(crate) accepted_deleted_lines: u64,
120 pub(crate) line_fingerprints: Vec<AcceptedLineFingerprint>,
121}
122
123#[derive(Serialize)]
124pub(crate) struct CodexAcceptedLineFingerprintsEventRequest {
125 pub(crate) event_type: &'static str,
126 pub(crate) event_params: CodexAcceptedLineFingerprintsEventParams,
127}
128
129#[derive(Serialize)]
130pub(crate) struct SkillInvocationEventRequest {
131 pub(crate) event_type: &'static str,
132 pub(crate) skill_id: String,
133 pub(crate) skill_name: String,
134 pub(crate) event_params: SkillInvocationEventParams,
135}
136
137#[derive(Serialize)]
138pub(crate) struct SkillInvocationEventParams {
139 pub(crate) product_client_id: Option<String>,
140 pub(crate) skill_scope: Option<String>,
141 pub(crate) plugin_id: Option<String>,
142 pub(crate) repo_url: Option<String>,
143 pub(crate) thread_id: Option<String>,
144 pub(crate) turn_id: Option<String>,
145 pub(crate) invoke_type: Option<InvocationType>,
146 pub(crate) model_slug: Option<String>,
147}
148
149#[derive(Clone, Serialize)]
150pub(crate) struct CodexAppServerClientMetadata {
151 pub(crate) product_client_id: String,
152 pub(crate) client_name: Option<String>,
153 pub(crate) client_version: Option<String>,
154 pub(crate) rpc_transport: AppServerRpcTransport,
155 pub(crate) experimental_api_enabled: Option<bool>,
156}
157
158#[derive(Clone, Serialize)]
159pub(crate) struct CodexRuntimeMetadata {
160 pub(crate) codex_rs_version: String,
161 pub(crate) runtime_os: String,
162 pub(crate) runtime_os_version: String,
163 pub(crate) runtime_arch: String,
164}
165
166#[derive(Serialize)]
167pub(crate) struct ThreadInitializedEventParams {
168 pub(crate) thread_id: String,
169 pub(crate) session_id: String,
170 pub(crate) app_server_client: CodexAppServerClientMetadata,
171 pub(crate) runtime: CodexRuntimeMetadata,
172 pub(crate) model: String,
173 pub(crate) ephemeral: bool,
174 pub(crate) thread_source: Option<ThreadSource>,
175 pub(crate) initialization_mode: ThreadInitializationMode,
176 pub(crate) subagent_source: Option<String>,
177 pub(crate) parent_thread_id: Option<String>,
178 pub(crate) forked_from_thread_id: Option<String>,
179 pub(crate) created_at: u64,
180}
181
182#[derive(Serialize)]
183pub(crate) struct ThreadInitializedEvent {
184 pub(crate) event_type: &'static str,
185 pub(crate) event_params: ThreadInitializedEventParams,
186}
187
188#[derive(Serialize)]
189pub(crate) struct GuardianReviewEventRequest {
190 pub(crate) event_type: &'static str,
191 pub(crate) event_params: GuardianReviewEventPayload,
192}
193
194#[derive(Clone, Copy, Debug, Serialize)]
195#[serde(rename_all = "snake_case")]
196pub enum GuardianReviewDecision {
197 Approved,
198 Denied,
199 Aborted,
200}
201
202#[derive(Clone, Copy, Debug, Serialize)]
203#[serde(rename_all = "snake_case")]
204pub enum GuardianReviewTerminalStatus {
205 Approved,
206 Denied,
207 Aborted,
208 TimedOut,
209 FailedClosed,
210}
211
212#[derive(Clone, Copy, Debug, Serialize)]
213#[serde(rename_all = "snake_case")]
214pub enum GuardianReviewFailureReason {
215 Timeout,
216 Cancelled,
217 PromptBuildError,
218 SessionError,
219 ParseError,
220}
221
222#[derive(Clone, Copy, Debug, Serialize)]
223#[serde(rename_all = "snake_case")]
224pub enum GuardianReviewSessionKind {
225 TrunkNew,
226 TrunkReused,
227 EphemeralForked,
228}
229
230#[derive(Clone, Copy, Debug, Serialize)]
231#[serde(rename_all = "snake_case")]
232pub enum GuardianApprovalRequestSource {
233 MainTurn,
235 DelegatedSubagent,
238}
239
240#[derive(Clone, Debug, Serialize)]
241#[serde(tag = "type", rename_all = "snake_case")]
242pub enum GuardianReviewedAction {
243 Shell {
244 sandbox_permissions: SandboxPermissions,
245 additional_permissions: Option<AdditionalPermissionProfile>,
246 },
247 UnifiedExec {
248 sandbox_permissions: SandboxPermissions,
249 additional_permissions: Option<AdditionalPermissionProfile>,
250 tty: bool,
251 },
252 Execve {
253 source: GuardianCommandSource,
254 program: String,
255 additional_permissions: Option<AdditionalPermissionProfile>,
256 },
257 ApplyPatch {},
258 NetworkAccess {
259 protocol: NetworkApprovalProtocol,
260 port: u16,
261 },
262 McpToolCall {
263 server: String,
264 tool_name: String,
265 connector_id: Option<String>,
266 connector_name: Option<String>,
267 tool_title: Option<String>,
268 },
269 RequestPermissions {},
270}
271
272#[derive(Clone, Serialize)]
273pub struct GuardianReviewEventParams {
274 pub thread_id: String,
275 pub turn_id: String,
276 pub review_id: String,
277 pub target_item_id: Option<String>,
278 pub approval_request_source: GuardianApprovalRequestSource,
279 pub reviewed_action: GuardianReviewedAction,
280 pub reviewed_action_truncated: bool,
281 pub decision: GuardianReviewDecision,
282 pub terminal_status: GuardianReviewTerminalStatus,
283 pub failure_reason: Option<GuardianReviewFailureReason>,
284 pub attempt_count: i64,
285 pub risk_level: Option<GuardianRiskLevel>,
286 pub user_authorization: Option<GuardianUserAuthorization>,
287 pub outcome: Option<GuardianAssessmentOutcome>,
288 pub guardian_thread_id: Option<String>,
289 pub guardian_session_kind: Option<GuardianReviewSessionKind>,
290 pub guardian_model: Option<String>,
291 pub guardian_reasoning_effort: Option<String>,
292 pub guardian_default_review_model_id: Option<String>,
293 pub guardian_catalog_contains_auto_review: Option<bool>,
294 pub guardian_review_model_overridden: Option<bool>,
295 pub guardian_review_model_override: Option<String>,
296 pub guardian_model_provider_id: Option<String>,
297 pub had_prior_review_context: Option<bool>,
298 pub review_timeout_ms: u64,
299 pub tool_call_count: Option<u64>,
300 pub time_to_first_token_ms: Option<u64>,
301 pub completion_latency_ms: Option<u64>,
302 pub started_at: u64,
303 pub completed_at: Option<u64>,
304 pub input_tokens: Option<i64>,
305 pub cached_input_tokens: Option<i64>,
306 pub cache_write_input_tokens: Option<i64>,
307 pub output_tokens: Option<i64>,
308 pub reasoning_output_tokens: Option<i64>,
309 pub total_tokens: Option<i64>,
310}
311
312pub struct GuardianReviewTrackContext {
313 thread_id: String,
314 turn_id: String,
315 review_id: String,
316 target_item_id: Option<String>,
317 approval_request_source: GuardianApprovalRequestSource,
318 reviewed_action: GuardianReviewedAction,
319 review_timeout_ms: u64,
320 pub started_at_ms: u64,
321 started_instant: Instant,
322}
323
324impl GuardianReviewTrackContext {
325 pub fn new(
326 thread_id: String,
327 turn_id: String,
328 review_id: String,
329 target_item_id: Option<String>,
330 approval_request_source: GuardianApprovalRequestSource,
331 reviewed_action: GuardianReviewedAction,
332 review_timeout_ms: u64,
333 ) -> Self {
334 Self {
335 thread_id,
336 turn_id,
337 review_id,
338 target_item_id,
339 approval_request_source,
340 reviewed_action,
341 review_timeout_ms,
342 started_at_ms: now_unix_millis(),
343 started_instant: Instant::now(),
344 }
345 }
346
347 pub(crate) fn event_params(
348 &self,
349 result: GuardianReviewAnalyticsResult,
350 completed_at_ms: u64,
351 ) -> GuardianReviewEventParams {
352 GuardianReviewEventParams {
353 thread_id: self.thread_id.clone(),
354 turn_id: self.turn_id.clone(),
355 review_id: self.review_id.clone(),
356 target_item_id: self.target_item_id.clone(),
357 approval_request_source: self.approval_request_source,
358 reviewed_action: self.reviewed_action.clone(),
359 reviewed_action_truncated: result.reviewed_action_truncated,
360 decision: result.decision,
361 terminal_status: result.terminal_status,
362 failure_reason: result.failure_reason,
363 attempt_count: result.attempt_count,
364 risk_level: result.risk_level,
365 user_authorization: result.user_authorization,
366 outcome: result.outcome,
367 guardian_thread_id: result.guardian_thread_id,
368 guardian_session_kind: result.guardian_session_kind,
369 guardian_model: result.guardian_model,
370 guardian_reasoning_effort: result.guardian_reasoning_effort,
371 guardian_default_review_model_id: result.guardian_default_review_model_id,
372 guardian_catalog_contains_auto_review: result.guardian_catalog_contains_auto_review,
373 guardian_review_model_overridden: result.guardian_review_model_overridden,
374 guardian_review_model_override: result.guardian_review_model_override,
375 guardian_model_provider_id: result.guardian_model_provider_id,
376 had_prior_review_context: result.had_prior_review_context,
377 review_timeout_ms: self.review_timeout_ms,
378 tool_call_count: None,
380 time_to_first_token_ms: result.time_to_first_token_ms,
381 completion_latency_ms: Some(self.started_instant.elapsed().as_millis() as u64),
382 started_at: self.started_at_ms / 1_000,
383 completed_at: Some(completed_at_ms / 1_000),
384 input_tokens: result.token_usage.as_ref().map(|usage| usage.input_tokens),
385 cached_input_tokens: result
386 .token_usage
387 .as_ref()
388 .map(|usage| usage.cached_input_tokens),
389 cache_write_input_tokens: result
390 .token_usage
391 .as_ref()
392 .map(|usage| usage.cache_write_input_tokens),
393 output_tokens: result.token_usage.as_ref().map(|usage| usage.output_tokens),
394 reasoning_output_tokens: result
395 .token_usage
396 .as_ref()
397 .map(|usage| usage.reasoning_output_tokens),
398 total_tokens: result.token_usage.as_ref().map(|usage| usage.total_tokens),
399 }
400 }
401}
402
403#[derive(Debug)]
404pub struct GuardianReviewAnalyticsResult {
405 pub decision: GuardianReviewDecision,
406 pub terminal_status: GuardianReviewTerminalStatus,
407 pub failure_reason: Option<GuardianReviewFailureReason>,
408 pub attempt_count: i64,
409 pub risk_level: Option<GuardianRiskLevel>,
410 pub user_authorization: Option<GuardianUserAuthorization>,
411 pub outcome: Option<GuardianAssessmentOutcome>,
412 pub guardian_thread_id: Option<String>,
413 pub guardian_session_kind: Option<GuardianReviewSessionKind>,
414 pub guardian_model: Option<String>,
415 pub guardian_reasoning_effort: Option<String>,
416 pub guardian_default_review_model_id: Option<String>,
417 pub guardian_catalog_contains_auto_review: Option<bool>,
418 pub guardian_review_model_overridden: Option<bool>,
419 pub guardian_review_model_override: Option<String>,
420 pub guardian_model_provider_id: Option<String>,
421 pub had_prior_review_context: Option<bool>,
422 pub reviewed_action_truncated: bool,
423 pub token_usage: Option<TokenUsage>,
424 pub time_to_first_token_ms: Option<u64>,
425}
426
427impl GuardianReviewAnalyticsResult {
428 pub fn without_session() -> Self {
429 Self {
430 decision: GuardianReviewDecision::Denied,
431 terminal_status: GuardianReviewTerminalStatus::FailedClosed,
432 failure_reason: None,
433 attempt_count: 1,
434 risk_level: None,
435 user_authorization: None,
436 outcome: None,
437 guardian_thread_id: None,
438 guardian_session_kind: None,
439 guardian_model: None,
440 guardian_reasoning_effort: None,
441 guardian_default_review_model_id: None,
442 guardian_catalog_contains_auto_review: None,
443 guardian_review_model_overridden: None,
444 guardian_review_model_override: None,
445 guardian_model_provider_id: None,
446 had_prior_review_context: None,
447 reviewed_action_truncated: false,
448 token_usage: None,
449 time_to_first_token_ms: None,
450 }
451 }
452
453 pub fn from_session(params: GuardianReviewSessionAnalyticsParams) -> Self {
454 Self {
455 guardian_thread_id: Some(params.guardian_thread_id),
456 guardian_session_kind: Some(params.guardian_session_kind),
457 guardian_model: Some(params.guardian_model),
458 guardian_reasoning_effort: params.guardian_reasoning_effort,
459 guardian_default_review_model_id: Some(params.guardian_default_review_model_id),
460 guardian_catalog_contains_auto_review: Some(
461 params.guardian_catalog_contains_auto_review,
462 ),
463 guardian_review_model_overridden: Some(params.guardian_review_model_overridden),
464 guardian_review_model_override: params.guardian_review_model_override,
465 guardian_model_provider_id: Some(params.guardian_model_provider_id),
466 had_prior_review_context: Some(params.had_prior_review_context),
467 ..Self::without_session()
468 }
469 }
470}
471
472pub struct GuardianReviewSessionAnalyticsParams {
473 pub guardian_thread_id: String,
474 pub guardian_session_kind: GuardianReviewSessionKind,
475 pub guardian_model: String,
476 pub guardian_reasoning_effort: Option<String>,
477 pub guardian_default_review_model_id: String,
478 pub guardian_catalog_contains_auto_review: bool,
479 pub guardian_review_model_overridden: bool,
480 pub guardian_review_model_override: Option<String>,
481 pub guardian_model_provider_id: String,
482 pub had_prior_review_context: bool,
483}
484
485#[derive(Serialize)]
486pub(crate) struct GuardianReviewEventPayload {
487 pub(crate) session_id: String,
488 pub(crate) app_server_client: CodexAppServerClientMetadata,
489 pub(crate) runtime: CodexRuntimeMetadata,
490 #[serde(flatten)]
491 pub(crate) guardian_review: GuardianReviewEventParams,
492}
493
494#[allow(dead_code)]
495#[derive(Clone, Copy, Debug, Serialize)]
496#[serde(rename_all = "snake_case")]
497pub(crate) enum FinalApprovalOutcome {
498 Unknown,
499 NotNeeded,
500 ConfigAllowed,
501 PolicyForbidden,
502 GuardianApproved,
503 GuardianDenied,
504 GuardianAborted,
505 UserApproved,
506 UserApprovedForSession,
507 UserDenied,
508 UserAborted,
509}
510
511#[allow(dead_code)]
512#[derive(Clone, Copy, Debug, Serialize)]
513#[serde(rename_all = "snake_case")]
514pub(crate) enum ToolItemTerminalStatus {
515 Completed,
516 Failed,
517 Rejected,
518 Interrupted,
519}
520
521#[allow(dead_code)]
522#[derive(Clone, Copy, Debug, Serialize)]
523#[serde(rename_all = "snake_case")]
524pub(crate) enum ToolItemFailureKind {
525 ToolError,
526 ApprovalDenied,
527 ApprovalAborted,
528 SandboxDenied,
529 PolicyForbidden,
530}
531
532#[derive(Serialize)]
533pub(crate) struct CodexToolItemEventBase {
534 pub(crate) thread_id: String,
535 pub(crate) session_id: String,
536 pub(crate) turn_id: String,
537 pub(crate) item_id: String,
540 pub(crate) app_server_client: CodexAppServerClientMetadata,
541 pub(crate) runtime: CodexRuntimeMetadata,
542 pub(crate) thread_source: Option<ThreadSource>,
543 pub(crate) subagent_source: Option<String>,
544 pub(crate) parent_thread_id: Option<String>,
545 pub(crate) tool_name: String,
546 pub(crate) started_at_ms: u64,
547 pub(crate) completed_at_ms: u64,
548 pub(crate) duration_ms: Option<u64>,
551 pub(crate) execution_duration_ms: Option<u64>,
552 pub(crate) review_count: u64,
553 pub(crate) guardian_review_count: u64,
554 pub(crate) user_review_count: u64,
555 pub(crate) final_approval_outcome: FinalApprovalOutcome,
556 pub(crate) terminal_status: ToolItemTerminalStatus,
557 pub(crate) failure_kind: Option<ToolItemFailureKind>,
558 pub(crate) requested_additional_permissions: bool,
559 pub(crate) requested_network_access: bool,
560}
561
562#[allow(dead_code)]
563#[derive(Clone, Copy, Debug, Serialize)]
564#[serde(rename_all = "snake_case")]
565pub(crate) enum ReviewSubjectKind {
566 CommandExecution,
567 FileChange,
568 McpToolCall,
569 Permissions,
570 NetworkAccess,
571}
572
573#[allow(dead_code)]
574#[derive(Clone, Copy, Debug, Serialize)]
575#[serde(rename_all = "snake_case")]
576pub(crate) enum Reviewer {
577 Guardian,
578 User,
579}
580
581#[allow(dead_code)]
582#[derive(Clone, Copy, Debug, Serialize)]
583#[serde(rename_all = "snake_case")]
584pub(crate) enum ReviewTrigger {
585 Initial,
586 SandboxDenial,
587 NetworkPolicyDenial,
588 ExecveIntercept,
589}
590
591#[allow(dead_code)]
592#[derive(Clone, Copy, Debug, Serialize)]
593#[serde(rename_all = "snake_case")]
594pub(crate) enum ReviewStatus {
595 Approved,
596 Denied,
597 Aborted,
598 TimedOut,
599}
600
601#[allow(dead_code)]
602#[derive(Clone, Copy, Debug, Serialize)]
603#[serde(rename_all = "snake_case")]
604pub(crate) enum ReviewResolution {
605 None,
606 SessionApproval,
607 ExecPolicyAmendment,
608 NetworkPolicyAmendment,
609}
610
611#[derive(Serialize)]
612pub(crate) struct CodexReviewEventParams {
613 pub(crate) thread_id: String,
614 pub(crate) turn_id: String,
615 pub(crate) item_id: Option<String>,
616 pub(crate) review_id: String,
617 pub(crate) app_server_client: CodexAppServerClientMetadata,
618 pub(crate) runtime: CodexRuntimeMetadata,
619 pub(crate) thread_source: Option<ThreadSource>,
620 pub(crate) subagent_source: Option<String>,
621 pub(crate) parent_thread_id: Option<String>,
622 pub(crate) subject_kind: ReviewSubjectKind,
623 pub(crate) subject_name: String,
624 pub(crate) reviewer: Reviewer,
625 pub(crate) trigger: ReviewTrigger,
626 pub(crate) status: ReviewStatus,
627 pub(crate) resolution: ReviewResolution,
628 pub(crate) started_at_ms: u64,
629 pub(crate) completed_at_ms: u64,
630 pub(crate) duration_ms: Option<u64>,
631}
632
633#[derive(Serialize)]
634pub(crate) struct CodexReviewEventRequest {
635 pub(crate) event_type: &'static str,
636 pub(crate) event_params: CodexReviewEventParams,
637}
638#[allow(dead_code)]
639#[derive(Clone, Copy, Debug, Serialize)]
640#[serde(rename_all = "snake_case")]
641pub(crate) enum WebSearchActionKind {
642 Search,
643 OpenPage,
644 FindInPage,
645 Other,
646}
647
648#[derive(Serialize)]
649pub(crate) struct CodexCommandExecutionEventParams {
650 #[serde(flatten)]
651 pub(crate) base: CodexToolItemEventBase,
652 pub(crate) command_execution_source: CommandExecutionSource,
653 pub(crate) exit_code: Option<i32>,
654 pub(crate) command_total_action_count: u64,
655 pub(crate) command_read_action_count: u64,
656 pub(crate) command_list_files_action_count: u64,
657 pub(crate) command_search_action_count: u64,
658 pub(crate) command_unknown_action_count: u64,
659}
660
661#[derive(Serialize)]
662pub(crate) struct CodexCommandExecutionEventRequest {
663 pub(crate) event_type: &'static str,
664 pub(crate) event_params: CodexCommandExecutionEventParams,
665}
666
667#[derive(Serialize)]
668pub(crate) struct CodexFileChangeEventParams {
669 #[serde(flatten)]
670 pub(crate) base: CodexToolItemEventBase,
671 pub(crate) file_change_count: u64,
672 pub(crate) file_add_count: u64,
673 pub(crate) file_update_count: u64,
674 pub(crate) file_delete_count: u64,
675 pub(crate) file_move_count: u64,
676}
677
678#[derive(Serialize)]
679pub(crate) struct CodexFileChangeEventRequest {
680 pub(crate) event_type: &'static str,
681 pub(crate) event_params: CodexFileChangeEventParams,
682}
683
684#[derive(Serialize)]
685pub(crate) struct CodexMcpToolCallEventParams {
686 #[serde(flatten)]
687 pub(crate) base: CodexToolItemEventBase,
688 pub(crate) mcp_server_name: String,
689 pub(crate) mcp_tool_name: String,
690 pub(crate) mcp_error_present: bool,
691 pub(crate) plugin_id: Option<String>,
692 pub(crate) connector_id: Option<String>,
693}
694
695#[derive(Serialize)]
696pub(crate) struct CodexMcpToolCallEventRequest {
697 pub(crate) event_type: &'static str,
698 pub(crate) event_params: CodexMcpToolCallEventParams,
699}
700
701#[derive(Serialize)]
702pub(crate) struct CodexDynamicToolCallEventParams {
703 #[serde(flatten)]
704 pub(crate) base: CodexToolItemEventBase,
705 pub(crate) dynamic_tool_name: String,
706 pub(crate) success: Option<bool>,
707 pub(crate) output_content_item_count: Option<u64>,
708 pub(crate) output_text_item_count: Option<u64>,
709 pub(crate) output_image_item_count: Option<u64>,
710 pub(crate) output_audio_item_count: Option<u64>,
711}
712
713#[derive(Serialize)]
714pub(crate) struct CodexDynamicToolCallEventRequest {
715 pub(crate) event_type: &'static str,
716 pub(crate) event_params: CodexDynamicToolCallEventParams,
717}
718
719#[derive(Serialize)]
720pub(crate) struct CodexCollabAgentToolCallEventParams {
721 #[serde(flatten)]
722 pub(crate) base: CodexToolItemEventBase,
723 pub(crate) sender_thread_id: String,
724 pub(crate) receiver_thread_count: u64,
725 pub(crate) receiver_thread_ids: Option<Vec<String>>,
726 pub(crate) requested_model: Option<String>,
727 pub(crate) requested_reasoning_effort: Option<String>,
728 pub(crate) agent_state_count: Option<u64>,
729 pub(crate) completed_agent_count: Option<u64>,
730 pub(crate) failed_agent_count: Option<u64>,
731}
732
733#[derive(Serialize)]
734pub(crate) struct CodexCollabAgentToolCallEventRequest {
735 pub(crate) event_type: &'static str,
736 pub(crate) event_params: CodexCollabAgentToolCallEventParams,
737}
738
739#[derive(Serialize)]
740pub(crate) struct CodexWebSearchEventParams {
741 #[serde(flatten)]
742 pub(crate) base: CodexToolItemEventBase,
743 pub(crate) web_search_action: Option<WebSearchActionKind>,
744 pub(crate) query_present: bool,
745 pub(crate) query_count: Option<u64>,
746}
747
748#[derive(Serialize)]
749pub(crate) struct CodexWebSearchEventRequest {
750 pub(crate) event_type: &'static str,
751 pub(crate) event_params: CodexWebSearchEventParams,
752}
753
754#[derive(Serialize)]
755pub(crate) struct CodexImageGenerationEventParams {
756 #[serde(flatten)]
757 pub(crate) base: CodexToolItemEventBase,
758 pub(crate) revised_prompt_present: bool,
759 pub(crate) saved_path_present: bool,
760}
761
762#[derive(Serialize)]
763pub(crate) struct CodexImageGenerationEventRequest {
764 pub(crate) event_type: &'static str,
765 pub(crate) event_params: CodexImageGenerationEventParams,
766}
767
768#[derive(Serialize)]
769pub(crate) struct CodexAppMetadata {
770 pub(crate) connector_id: Option<String>,
771 pub(crate) thread_id: Option<String>,
772 pub(crate) turn_id: Option<String>,
773 pub(crate) app_name: Option<String>,
774 pub(crate) product_client_id: Option<String>,
775 pub(crate) invoke_type: Option<InvocationType>,
776 pub(crate) model_slug: Option<String>,
777}
778
779#[derive(Serialize)]
780pub(crate) struct CodexAppMentionedEventRequest {
781 pub(crate) event_type: &'static str,
782 pub(crate) event_params: CodexAppMetadata,
783}
784
785#[derive(Serialize)]
786pub(crate) struct CodexAppUsedEventRequest {
787 pub(crate) event_type: &'static str,
788 pub(crate) event_params: CodexAppMetadata,
789}
790
791#[derive(Serialize)]
792pub(crate) struct CodexHookRunMetadata {
793 pub(crate) thread_id: Option<String>,
794 pub(crate) turn_id: Option<String>,
795 pub(crate) product_client_id: Option<String>,
796 pub(crate) model_slug: Option<String>,
797 pub(crate) hook_name: Option<String>,
798 pub(crate) hook_source: Option<&'static str>,
799 pub(crate) status: Option<HookRunStatus>,
800}
801
802#[derive(Serialize)]
803pub(crate) struct CodexHookRunEventRequest {
804 pub(crate) event_type: &'static str,
805 pub(crate) event_params: CodexHookRunMetadata,
806}
807
808#[derive(Serialize)]
809pub(crate) struct CodexCompactionEventParams {
810 pub(crate) thread_id: String,
811 pub(crate) session_id: String,
812 pub(crate) turn_id: String,
813 pub(crate) app_server_client: CodexAppServerClientMetadata,
814 pub(crate) runtime: CodexRuntimeMetadata,
815 pub(crate) thread_source: Option<ThreadSource>,
816 pub(crate) subagent_source: Option<String>,
817 pub(crate) parent_thread_id: Option<String>,
818 pub(crate) trigger: CompactionTrigger,
819 pub(crate) reason: CompactionReason,
820 pub(crate) implementation: CompactionImplementation,
821 pub(crate) phase: CompactionPhase,
822 pub(crate) strategy: CompactionStrategy,
823 pub(crate) status: CompactionStatus,
824 pub(crate) codex_error_kind: Option<CodexErrKind>,
825 pub(crate) codex_error_http_status_code: Option<u16>,
826 pub(crate) active_context_tokens_before: i64,
827 pub(crate) active_context_tokens_after: i64,
828 pub(crate) retained_image_count: Option<usize>,
829 pub(crate) compaction_summary_tokens: Option<i64>,
830 pub(crate) cached_input_tokens: Option<i64>,
831 pub(crate) cache_write_input_tokens: Option<i64>,
832 pub(crate) started_at: u64,
833 pub(crate) completed_at: u64,
834 pub(crate) duration_ms: Option<u64>,
835}
836
837#[derive(Serialize)]
838pub(crate) struct CodexCompactionEventRequest {
839 pub(crate) event_type: &'static str,
840 pub(crate) event_params: CodexCompactionEventParams,
841}
842
843#[derive(Serialize)]
844pub(crate) struct CodexGoalEventParams {
845 pub(crate) thread_id: String,
846 pub(crate) session_id: String,
847 pub(crate) turn_id: Option<String>,
848 pub(crate) app_server_client: CodexAppServerClientMetadata,
849 pub(crate) runtime: CodexRuntimeMetadata,
850 pub(crate) thread_source: Option<ThreadSource>,
851 pub(crate) subagent_source: Option<String>,
852 pub(crate) parent_thread_id: Option<String>,
853 pub(crate) goal_id: String,
854 pub(crate) event_kind: GoalEventKind,
855 pub(crate) goal_status: codex_state::ThreadGoalStatus,
856 pub(crate) has_token_budget: bool,
857 pub(crate) cumulative_tokens_accounted: Option<i64>,
858 pub(crate) cumulative_time_accounted_seconds: Option<i64>,
859}
860
861#[derive(Serialize)]
862pub(crate) struct CodexGoalEventRequest {
863 pub(crate) event_type: &'static str,
864 pub(crate) event_params: CodexGoalEventParams,
865}
866
867#[derive(Serialize)]
868pub(crate) struct CodexTurnEventParams {
869 pub(crate) thread_id: String,
870 pub(crate) session_id: String,
871 pub(crate) turn_id: String,
872 pub(crate) submission_type: Option<TurnSubmissionType>,
875 pub(crate) app_server_client: CodexAppServerClientMetadata,
876 pub(crate) runtime: CodexRuntimeMetadata,
877 pub(crate) ephemeral: bool,
878 pub(crate) thread_source: Option<ThreadSource>,
879 pub(crate) initialization_mode: ThreadInitializationMode,
880 pub(crate) subagent_source: Option<String>,
881 pub(crate) parent_thread_id: Option<String>,
882 pub(crate) model: Option<String>,
883 pub(crate) model_provider: String,
884 pub(crate) sandbox_policy: Option<&'static str>,
885 pub(crate) reasoning_effort: Option<String>,
886 pub(crate) reasoning_summary: Option<String>,
887 pub(crate) service_tier: String,
888 pub(crate) approval_policy: String,
889 pub(crate) approvals_reviewer: String,
890 pub(crate) sandbox_network_access: bool,
891 pub(crate) collaboration_mode: Option<&'static str>,
892 pub(crate) personality: Option<String>,
893 pub(crate) workspace_kind: Option<String>,
894 pub(crate) num_input_images: usize,
895 pub(crate) is_first_turn: bool,
896 pub(crate) status: Option<TurnStatus>,
897 pub(crate) turn_error: Option<CodexErrorInfo>,
898 pub(crate) codex_error_kind: Option<CodexErrKind>,
899 pub(crate) codex_error_http_status_code: Option<u16>,
900 pub(crate) steer_count: Option<usize>,
901 pub(crate) total_tool_call_count: Option<usize>,
902 pub(crate) shell_command_count: Option<usize>,
903 pub(crate) file_change_count: Option<usize>,
904 pub(crate) mcp_tool_call_count: Option<usize>,
905 pub(crate) dynamic_tool_call_count: Option<usize>,
906 pub(crate) subagent_tool_call_count: Option<usize>,
907 pub(crate) web_search_count: Option<usize>,
908 pub(crate) image_generation_count: Option<usize>,
909 pub(crate) input_tokens: Option<i64>,
910 pub(crate) cached_input_tokens: Option<i64>,
911 pub(crate) cache_write_input_tokens: Option<i64>,
912 pub(crate) output_tokens: Option<i64>,
913 pub(crate) reasoning_output_tokens: Option<i64>,
914 pub(crate) total_tokens: Option<i64>,
915 pub(crate) before_first_sampling_ms: u64,
916 pub(crate) sampling_ms: u64,
917 pub(crate) between_sampling_overhead_ms: u64,
918 pub(crate) tool_blocking_ms: u64,
919 pub(crate) after_last_sampling_ms: u64,
920 pub(crate) sampling_request_count: u32,
921 pub(crate) sampling_retry_count: u32,
922 pub(crate) duration_ms: Option<u64>,
923 pub(crate) started_at: Option<u64>,
924 pub(crate) completed_at: Option<u64>,
925}
926
927#[derive(Serialize)]
928pub(crate) struct CodexTurnEventRequest {
929 pub(crate) event_type: &'static str,
930 pub(crate) event_params: CodexTurnEventParams,
931}
932
933#[derive(Serialize)]
934pub(crate) struct CodexTurnSteerEventParams {
935 pub(crate) thread_id: String,
936 pub(crate) session_id: String,
937 pub(crate) expected_turn_id: Option<String>,
938 pub(crate) accepted_turn_id: Option<String>,
939 pub(crate) app_server_client: CodexAppServerClientMetadata,
940 pub(crate) runtime: CodexRuntimeMetadata,
941 pub(crate) thread_source: Option<ThreadSource>,
942 pub(crate) subagent_source: Option<String>,
943 pub(crate) parent_thread_id: Option<String>,
944 pub(crate) num_input_images: usize,
945 pub(crate) result: TurnSteerResult,
946 pub(crate) rejection_reason: Option<TurnSteerRejectionReason>,
947 pub(crate) created_at: u64,
948}
949
950#[derive(Serialize)]
951pub(crate) struct CodexTurnSteerEventRequest {
952 pub(crate) event_type: &'static str,
953 pub(crate) event_params: CodexTurnSteerEventParams,
954}
955
956#[derive(Serialize)]
957pub(crate) struct CodexPluginMetadata {
958 pub(crate) plugin_id: Option<String>,
959 pub(crate) remote_plugin_id: Option<String>,
960 pub(crate) plugin_name: Option<String>,
961 pub(crate) marketplace_name: Option<String>,
962 pub(crate) has_skills: Option<bool>,
963 pub(crate) mcp_server_count: Option<usize>,
964 pub(crate) connector_ids: Option<Vec<String>>,
965 pub(crate) product_client_id: Option<String>,
966}
967
968#[derive(Serialize)]
969pub(crate) struct CodexPluginUsedMetadata {
970 #[serde(flatten)]
971 pub(crate) plugin: CodexPluginMetadata,
972 pub(crate) mcp_server_names: Option<Vec<String>>,
973 pub(crate) thread_id: Option<String>,
974 pub(crate) turn_id: Option<String>,
975 pub(crate) model_slug: Option<String>,
976}
977
978#[derive(Serialize)]
979pub(crate) struct CodexPluginInstallRequestedPluginMetadata {
980 pub(crate) plugin_id: String,
981 pub(crate) remote_plugin_id: Option<String>,
982 pub(crate) plugin_name: String,
983 pub(crate) connector_ids: Vec<String>,
984}
985
986#[derive(Serialize)]
987pub(crate) struct CodexPluginInstallRequestedMetadata {
988 pub(crate) suggestion_id: String,
989 pub(crate) plugins: Vec<CodexPluginInstallRequestedPluginMetadata>,
990 pub(crate) source: crate::facts::PluginInstallRequestSource,
991 pub(crate) thread_id: String,
992 pub(crate) turn_id: String,
993 pub(crate) model_slug: String,
994 pub(crate) product_client_id: Option<String>,
995}
996
997#[derive(Serialize)]
998pub(crate) struct CodexPluginInstallRequestedEventRequest {
999 pub(crate) event_type: &'static str,
1000 pub(crate) event_params: CodexPluginInstallRequestedMetadata,
1001}
1002
1003#[derive(Serialize)]
1004pub(crate) struct CodexPluginEventRequest {
1005 pub(crate) event_type: &'static str,
1006 pub(crate) event_params: CodexPluginMetadata,
1007}
1008
1009#[derive(Serialize)]
1010pub(crate) struct CodexPluginInstallFailedMetadata {
1011 #[serde(flatten)]
1012 pub(crate) plugin: CodexPluginMetadata,
1013 pub(crate) source: crate::facts::PluginInstallSource,
1014 pub(crate) error_type: String,
1015 pub(crate) sub_error_type: Option<String>,
1016}
1017
1018#[derive(Serialize)]
1019pub(crate) struct CodexPluginInstallFailedEventRequest {
1020 pub(crate) event_type: &'static str,
1021 pub(crate) event_params: CodexPluginInstallFailedMetadata,
1022}
1023
1024#[derive(Serialize)]
1025pub(crate) struct CodexOnboardingExternalAgentImportCompleteMetadata {
1026 pub(crate) import_id: String,
1027 pub(crate) source: String,
1028 pub(crate) provider_id: String,
1029 #[serde(rename = "type")]
1030 pub(crate) item_type: String,
1031 pub(crate) success_count: usize,
1032 pub(crate) failed_count: usize,
1033 pub(crate) product_client_id: Option<String>,
1034}
1035
1036#[derive(Serialize)]
1037pub(crate) struct CodexOnboardingExternalAgentImportCompleteEventRequest {
1038 pub(crate) event_type: &'static str,
1039 pub(crate) event_params: CodexOnboardingExternalAgentImportCompleteMetadata,
1040}
1041
1042#[derive(Serialize)]
1043pub(crate) struct CodexOnboardingExternalAgentImportFailureMetadata {
1044 pub(crate) import_id: String,
1045 pub(crate) source: String,
1046 pub(crate) provider_id: String,
1047 #[serde(rename = "type")]
1048 pub(crate) item_type: String,
1049 pub(crate) failure_stage: String,
1050 pub(crate) error_type: String,
1051 pub(crate) sub_error_type: Option<String>,
1052 pub(crate) product_client_id: Option<String>,
1053}
1054
1055#[derive(Serialize)]
1056pub(crate) struct CodexOnboardingExternalAgentImportFailureEventRequest {
1057 pub(crate) event_type: &'static str,
1058 pub(crate) event_params: CodexOnboardingExternalAgentImportFailureMetadata,
1059}
1060
1061#[derive(Serialize)]
1062pub(crate) struct CodexPluginUsedEventRequest {
1063 pub(crate) event_type: &'static str,
1064 pub(crate) event_params: CodexPluginUsedMetadata,
1065}
1066
1067pub(crate) fn plugin_state_event_type(state: PluginState) -> &'static str {
1068 match state {
1069 PluginState::Installed => "codex_plugin_installed",
1070 PluginState::Uninstalled => "codex_plugin_uninstalled",
1071 PluginState::Enabled => "codex_plugin_enabled",
1072 PluginState::Disabled => "codex_plugin_disabled",
1073 }
1074}
1075
1076pub(crate) fn codex_app_metadata(
1077 tracking: &TrackEventsContext,
1078 app: AppInvocation,
1079) -> CodexAppMetadata {
1080 CodexAppMetadata {
1081 connector_id: app.connector_id,
1082 thread_id: Some(tracking.thread_id.clone()),
1083 turn_id: Some(tracking.turn_id.clone()),
1084 app_name: app.app_name,
1085 product_client_id: Some(tracking.product_client_id.clone()),
1086 invoke_type: app.invocation_type,
1087 model_slug: Some(tracking.model_slug.clone()),
1088 }
1089}
1090
1091pub(crate) fn codex_plugin_metadata(plugin: PluginTelemetryMetadata) -> CodexPluginMetadata {
1092 codex_plugin_metadata_with_product_client_id(plugin, originator().value)
1093}
1094
1095fn codex_plugin_metadata_with_product_client_id(
1096 plugin: PluginTelemetryMetadata,
1097 product_client_id: String,
1098) -> CodexPluginMetadata {
1099 let PluginTelemetryMetadata {
1100 plugin_id,
1101 remote_plugin_id,
1102 capability_summary,
1103 } = plugin;
1104 CodexPluginMetadata {
1105 plugin_id: plugin_id.as_ref().map(PluginId::as_key),
1106 remote_plugin_id,
1107 plugin_name: plugin_id
1108 .as_ref()
1109 .map(|plugin_id| plugin_id.plugin_name.clone()),
1110 marketplace_name: plugin_id.map(|plugin_id| plugin_id.marketplace_name),
1111 has_skills: capability_summary
1112 .as_ref()
1113 .map(|summary| summary.has_skills),
1114 mcp_server_count: capability_summary
1115 .as_ref()
1116 .map(|summary| summary.mcp_server_names.len()),
1117 connector_ids: capability_summary.map(|summary| {
1118 summary
1119 .app_connector_ids
1120 .into_iter()
1121 .map(|connector_id| connector_id.0)
1122 .collect()
1123 }),
1124 product_client_id: Some(product_client_id),
1125 }
1126}
1127
1128pub(crate) fn codex_plugin_install_requested_metadata(
1129 tracking: &TrackEventsContext,
1130 request: PluginInstallRequested,
1131) -> CodexPluginInstallRequestedMetadata {
1132 CodexPluginInstallRequestedMetadata {
1133 suggestion_id: request.suggestion_id,
1134 plugins: request
1135 .plugins
1136 .into_iter()
1137 .map(|plugin| CodexPluginInstallRequestedPluginMetadata {
1138 plugin_id: plugin.plugin_id,
1139 remote_plugin_id: plugin.remote_plugin_id,
1140 plugin_name: plugin.plugin_name,
1141 connector_ids: plugin.connector_ids,
1142 })
1143 .collect(),
1144 source: request.source,
1145 thread_id: tracking.thread_id.clone(),
1146 turn_id: tracking.turn_id.clone(),
1147 model_slug: tracking.model_slug.clone(),
1148 product_client_id: Some(originator().value),
1149 }
1150}
1151
1152pub(crate) fn codex_compaction_event_params(
1153 input: CodexCompactionEvent,
1154 session_id: String,
1155 app_server_client: CodexAppServerClientMetadata,
1156 runtime: CodexRuntimeMetadata,
1157 thread_source: Option<ThreadSource>,
1158 subagent_source: Option<String>,
1159 parent_thread_id: Option<String>,
1160) -> CodexCompactionEventParams {
1161 CodexCompactionEventParams {
1162 thread_id: input.thread_id,
1163 session_id,
1164 turn_id: input.turn_id,
1165 app_server_client,
1166 runtime,
1167 thread_source,
1168 subagent_source,
1169 parent_thread_id,
1170 trigger: input.trigger,
1171 reason: input.reason,
1172 implementation: input.implementation,
1173 phase: input.phase,
1174 strategy: input.strategy,
1175 status: input.status,
1176 codex_error_kind: input.codex_error_kind,
1177 codex_error_http_status_code: input.codex_error_http_status_code,
1178 active_context_tokens_before: input.active_context_tokens_before,
1179 active_context_tokens_after: input.active_context_tokens_after,
1180 retained_image_count: input.retained_image_count,
1181 compaction_summary_tokens: input.compaction_summary_tokens,
1182 cached_input_tokens: input.cached_input_tokens,
1183 cache_write_input_tokens: input.cache_write_input_tokens,
1184 started_at: input.started_at,
1185 completed_at: input.completed_at,
1186 duration_ms: input.duration_ms,
1187 }
1188}
1189
1190pub(crate) fn codex_goal_event_params(
1191 input: CodexGoalEvent,
1192 session_id: String,
1193 app_server_client: CodexAppServerClientMetadata,
1194 runtime: CodexRuntimeMetadata,
1195 thread_source: Option<ThreadSource>,
1196 subagent_source: Option<String>,
1197 parent_thread_id: Option<String>,
1198) -> CodexGoalEventParams {
1199 CodexGoalEventParams {
1200 thread_id: input.thread_id,
1201 session_id,
1202 turn_id: input.turn_id,
1203 app_server_client,
1204 runtime,
1205 thread_source,
1206 subagent_source,
1207 parent_thread_id,
1208 goal_id: input.goal_id,
1209 event_kind: input.event_kind,
1210 goal_status: input.goal_status,
1211 has_token_budget: input.has_token_budget,
1212 cumulative_tokens_accounted: input.cumulative_tokens_accounted,
1213 cumulative_time_accounted_seconds: input.cumulative_time_accounted_seconds,
1214 }
1215}
1216
1217pub(crate) fn codex_plugin_used_metadata(
1218 tracking: &TrackEventsContext,
1219 plugin: PluginTelemetryMetadata,
1220) -> CodexPluginUsedMetadata {
1221 let mcp_server_names = plugin
1222 .capability_summary
1223 .as_ref()
1224 .map(|summary| summary.mcp_server_names.clone());
1225 CodexPluginUsedMetadata {
1226 plugin: codex_plugin_metadata_with_product_client_id(
1227 plugin,
1228 tracking.product_client_id.clone(),
1229 ),
1230 mcp_server_names,
1231 thread_id: Some(tracking.thread_id.clone()),
1232 turn_id: Some(tracking.turn_id.clone()),
1233 model_slug: Some(tracking.model_slug.clone()),
1234 }
1235}
1236
1237pub(crate) fn codex_hook_run_metadata(
1238 tracking: &TrackEventsContext,
1239 hook: HookRunFact,
1240) -> CodexHookRunMetadata {
1241 CodexHookRunMetadata {
1242 thread_id: Some(tracking.thread_id.clone()),
1243 turn_id: Some(tracking.turn_id.clone()),
1244 product_client_id: Some(tracking.product_client_id.clone()),
1245 model_slug: Some(tracking.model_slug.clone()),
1246 hook_name: Some(analytics_hook_event_name(hook.event_name).to_owned()),
1247 hook_source: Some(analytics_hook_source(hook.hook_source)),
1248 status: Some(analytics_hook_status(hook.status)),
1249 }
1250}
1251
1252fn analytics_hook_event_name(event_name: HookEventName) -> &'static str {
1253 match event_name {
1254 HookEventName::PreToolUse => "PreToolUse",
1255 HookEventName::PermissionRequest => "PermissionRequest",
1256 HookEventName::PostToolUse => "PostToolUse",
1257 HookEventName::PreCompact => "PreCompact",
1258 HookEventName::PostCompact => "PostCompact",
1259 HookEventName::SessionStart => "SessionStart",
1260 HookEventName::SessionEnd => "SessionEnd",
1261 HookEventName::UserPromptSubmit => "UserPromptSubmit",
1262 HookEventName::SubagentStart => "SubagentStart",
1263 HookEventName::SubagentStop => "SubagentStop",
1264 HookEventName::Stop => "Stop",
1265 }
1266}
1267
1268fn analytics_hook_source(source: HookSource) -> &'static str {
1269 match source {
1270 HookSource::System => "system",
1271 HookSource::User => "user",
1272 HookSource::Project => "project",
1273 HookSource::Mdm => "mdm",
1274 HookSource::SessionFlags => "session_flags",
1275 HookSource::Plugin => "plugin",
1276 HookSource::CloudRequirements => "cloud_requirements",
1277 HookSource::CloudManagedConfig => "cloud_managed_config",
1278 HookSource::LegacyManagedConfigFile => "legacy_managed_config_file",
1279 HookSource::LegacyManagedConfigMdm => "legacy_managed_config_mdm",
1280 HookSource::Unknown => "unknown",
1281 }
1282}
1283
1284pub(crate) fn current_runtime_metadata() -> CodexRuntimeMetadata {
1285 let os_info = os_info::get();
1286 CodexRuntimeMetadata {
1287 codex_rs_version: env!("CARGO_PKG_VERSION").to_string(),
1288 runtime_os: std::env::consts::OS.to_string(),
1289 runtime_os_version: os_info.version().to_string(),
1290 runtime_arch: std::env::consts::ARCH.to_string(),
1291 }
1292}
1293
1294pub(crate) fn subagent_thread_started_event_request(
1295 input: SubAgentThreadStartedInput,
1296) -> ThreadInitializedEvent {
1297 let event_params = ThreadInitializedEventParams {
1298 thread_id: input.thread_id,
1299 session_id: input.session_id,
1300 app_server_client: CodexAppServerClientMetadata {
1301 product_client_id: input.product_client_id,
1302 client_name: Some(input.client_name),
1303 client_version: Some(input.client_version),
1304 rpc_transport: AppServerRpcTransport::InProcess,
1305 experimental_api_enabled: None,
1306 },
1307 runtime: current_runtime_metadata(),
1308 model: input.model,
1309 ephemeral: input.ephemeral,
1310 thread_source: Some(ThreadSource::Subagent),
1311 initialization_mode: ThreadInitializationMode::New,
1312 subagent_source: Some(subagent_source_name(&input.subagent_source)),
1313 parent_thread_id: input.parent_thread_id,
1314 forked_from_thread_id: input.forked_from_thread_id,
1315 created_at: input.created_at,
1316 };
1317 ThreadInitializedEvent {
1318 event_type: "codex_thread_initialized",
1319 event_params,
1320 }
1321}
1322
1323pub(crate) fn subagent_source_name(subagent_source: &SubAgentSource) -> String {
1324 subagent_source.kind().to_string()
1325}
1326
1327fn analytics_hook_status(status: HookRunStatus) -> HookRunStatus {
1328 match status {
1329 HookRunStatus::Running => HookRunStatus::Failed,
1331 other => other,
1332 }
1333}