mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
608b8b1cc6
## Why - Currently, there is no analytics event for `/goal` behavior - Existing events cannot identify goal execution or its resulting outcome - The original update in [#26182](https://github.com/openai/codex/pull/26182) was implemented before `/goal` moved into `codex-goal-extension`. ## What Changed - Adds `codex_goal_event` serialization and enrichment to `codex-analytics` - Emits goal events from the canonical `codex-goal-extension` mutation and accounting paths: - `created` when a new logical goal is persisted - `usage_accounted` when cumulative goal usage is persisted - `status_changed` when the stored goal status changes - `cleared` when the goal is deleted - Preserves causal `turn_id` for turn driven events and uses null attribution for external or idle lifecycle events - Changes goal deletion to return the deleted row so `cleared` retains the stable goal ID ## Event Details Includes standard analytics metadata along with goal specific fields: - `goal_id`: Stable ID stored in the local SQLite goal row and shared across the goal's events - `event_kind`: Observed operation (see the 4 lifecycle events cited in the above bullet) - `goal_status`: Resulting or last stored status: `active`, `paused`, `blocked`, `usage_limited`, etc. - `has_token_budget`: Indicates whether a token budget is configured - `turn_id`: Causal turn ID, or null when no causal turn exists - `cumulative_tokens_accounted`: Cumulative tokens on `usage_accounted` events; null otherwise - `cumulative_time_accounted_seconds`: Cumulative active time on `usage_accounted` events; null otherwise ## Validation - `just test -p codex-analytics -p codex-state -p codex-goal-extension` - `just test -p codex-core -E 'test(/goal/)'` - `just test -p codex-app-server` - `cargo build -p codex-analytics -p codex-core -p codex-state -p codex-app-server`
78 lines
2.4 KiB
Rust
78 lines
2.4 KiB
Rust
use codex_analytics::AnalyticsEventsClient;
|
|
use codex_analytics::CodexGoalEvent;
|
|
use codex_analytics::GoalEventKind;
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct GoalAnalytics {
|
|
client: AnalyticsEventsClient,
|
|
}
|
|
|
|
pub(crate) enum GoalEventAttribution<'a> {
|
|
Turn(&'a str),
|
|
NoTurn,
|
|
}
|
|
|
|
impl GoalAnalytics {
|
|
pub(crate) fn new(client: AnalyticsEventsClient) -> Self {
|
|
Self { client }
|
|
}
|
|
|
|
pub(crate) fn created(
|
|
&self,
|
|
goal: &codex_state::ThreadGoal,
|
|
attribution: GoalEventAttribution<'_>,
|
|
) {
|
|
self.track(goal, attribution, GoalEventKind::Created);
|
|
}
|
|
|
|
pub(crate) fn usage_accounted(
|
|
&self,
|
|
goal: &codex_state::ThreadGoal,
|
|
attribution: GoalEventAttribution<'_>,
|
|
) {
|
|
self.track(goal, attribution, GoalEventKind::UsageAccounted);
|
|
}
|
|
|
|
pub(crate) fn status_changed(
|
|
&self,
|
|
goal: &codex_state::ThreadGoal,
|
|
previous_status: Option<codex_state::ThreadGoalStatus>,
|
|
attribution: GoalEventAttribution<'_>,
|
|
) {
|
|
if previous_status.is_some_and(|status| status != goal.status) {
|
|
self.track(goal, attribution, GoalEventKind::StatusChanged);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn cleared(&self, goal: &codex_state::ThreadGoal) {
|
|
self.track(goal, GoalEventAttribution::NoTurn, GoalEventKind::Cleared);
|
|
}
|
|
|
|
fn track(
|
|
&self,
|
|
goal: &codex_state::ThreadGoal,
|
|
attribution: GoalEventAttribution<'_>,
|
|
event_kind: GoalEventKind,
|
|
) {
|
|
let (cumulative_tokens_accounted, cumulative_time_accounted_seconds) = match event_kind {
|
|
GoalEventKind::UsageAccounted => (Some(goal.tokens_used), Some(goal.time_used_seconds)),
|
|
GoalEventKind::Created | GoalEventKind::StatusChanged | GoalEventKind::Cleared => {
|
|
(None, None)
|
|
}
|
|
};
|
|
self.client.track_goal_event(CodexGoalEvent {
|
|
thread_id: goal.thread_id.to_string(),
|
|
turn_id: match attribution {
|
|
GoalEventAttribution::Turn(turn_id) => Some(turn_id.to_string()),
|
|
GoalEventAttribution::NoTurn => None,
|
|
},
|
|
goal_id: goal.goal_id.clone(),
|
|
event_kind,
|
|
goal_status: goal.status,
|
|
has_token_budget: goal.token_budget.is_some(),
|
|
cumulative_tokens_accounted,
|
|
cumulative_time_accounted_seconds,
|
|
});
|
|
}
|
|
}
|