mirror of
https://github.com/openai/codex.git
synced 2026-11-07 16:35:32 +00:00
Emit accepted line fingerprint analytics (#21601)
## Why Codex assisted-code attribution needs a client-side accepted-code source that does not upload raw code. This adds a hash-only analytics event derived from the turn diff so downstream attribution can compare accepted Codex lines against commit or PR diffs. ## What Changed - Parse accepted/effective added lines from the final turn diff and emit `codex_accepted_line_fingerprints` analytics. - Hash repo, path, and normalized line content before upload; raw code and raw diffs are not included in the event. - Chunk large fingerprint payloads and send accepted-line fingerprint events in isolated requests while preserving normal batching for other analytics events. - Canonicalize Git remote URLs before repo hashing so SSH/HTTPS GitHub remotes join to the same repo hash. - Add parser coverage for unified diff hunk lines that look like `+++` or `---` file headers. ## Verification - `cargo test -p codex-analytics` - `cargo test -p codex-git-utils canonicalize_git_remote_url` - `just fix -p codex-analytics` - `just bazel-lock-check` - `git diff --check`
This commit is contained in:
1 parent
9183503b97
commit
bbb6bf0a37
10 files changed
+882
-27
No files matched your search
@@ -1,6 +1,14 @@
|
||||
use super::AnalyticsEventsClient;
|
||||
use super::AnalyticsEventsQueue;
|
||||
use super::track_event_request_batches;
|
||||
use crate::events::CodexAcceptedLineFingerprintsEventParams;
|
||||
use crate::events::CodexAcceptedLineFingerprintsEventRequest;
|
||||
use crate::events::SkillInvocationEventParams;
|
||||
use crate::events::SkillInvocationEventRequest;
|
||||
use crate::events::TrackEventRequest;
|
||||
use crate::facts::AcceptedLineFingerprint;
|
||||
use crate::facts::AnalyticsFact;
|
||||
use crate::facts::InvocationType;
|
||||
use codex_app_server_protocol::ApprovalsReviewer as AppServerApprovalsReviewer;
|
||||
use codex_app_server_protocol::AskForApproval as AppServerAskForApproval;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
@@ -31,6 +39,47 @@ use std::sync::Mutex;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::error::TryRecvError;
|
||||
|
||||
fn sample_accepted_line_fingerprint_event(thread_id: &str) -> TrackEventRequest {
|
||||
TrackEventRequest::AcceptedLineFingerprints(Box::new(
|
||||
CodexAcceptedLineFingerprintsEventRequest {
|
||||
event_type: "codex_accepted_line_fingerprints",
|
||||
event_params: CodexAcceptedLineFingerprintsEventParams {
|
||||
event_type: "codex.accepted_line_fingerprints",
|
||||
turn_id: "turn-1".to_string(),
|
||||
thread_id: thread_id.to_string(),
|
||||
product_surface: Some("codex".to_string()),
|
||||
model_slug: Some("gpt-5.1-codex".to_string()),
|
||||
completed_at: 1,
|
||||
repo_hash: None,
|
||||
accepted_added_lines: 1,
|
||||
accepted_deleted_lines: 0,
|
||||
line_fingerprints: vec![AcceptedLineFingerprint {
|
||||
path_hash: "path-hash".to_string(),
|
||||
line_hash: "line-hash".to_string(),
|
||||
}],
|
||||
},
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn sample_regular_track_event(thread_id: &str) -> TrackEventRequest {
|
||||
TrackEventRequest::SkillInvocation(SkillInvocationEventRequest {
|
||||
event_type: "skill_invocation",
|
||||
skill_id: format!("skill-{thread_id}"),
|
||||
skill_name: "doc".to_string(),
|
||||
event_params: SkillInvocationEventParams {
|
||||
product_client_id: None,
|
||||
skill_scope: None,
|
||||
plugin_id: None,
|
||||
repo_url: None,
|
||||
thread_id: Some(thread_id.to_string()),
|
||||
turn_id: Some("turn-1".to_string()),
|
||||
invoke_type: Some(InvocationType::Explicit),
|
||||
model_slug: Some("gpt-5.1-codex".to_string()),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn client_with_receiver() -> (AnalyticsEventsClient, mpsc::Receiver<AnalyticsFact>) {
|
||||
let (sender, receiver) = mpsc::channel(8);
|
||||
let queue = AnalyticsEventsQueue {
|
||||
@@ -222,3 +271,23 @@ fn track_response_only_enqueues_analytics_relevant_responses() {
|
||||
);
|
||||
assert!(matches!(receiver.try_recv(), Err(TryRecvError::Empty)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn track_event_request_batches_only_isolates_accepted_line_fingerprint_events() {
|
||||
let batches = track_event_request_batches(vec![
|
||||
sample_regular_track_event("thread-1"),
|
||||
sample_regular_track_event("thread-2"),
|
||||
sample_accepted_line_fingerprint_event("thread-3"),
|
||||
sample_accepted_line_fingerprint_event("thread-4"),
|
||||
sample_regular_track_event("thread-5"),
|
||||
sample_regular_track_event("thread-6"),
|
||||
]);
|
||||
|
||||
assert_eq!(batches.len(), 4);
|
||||
assert_eq!(batches[0].len(), 2);
|
||||
assert_eq!(batches[1].len(), 1);
|
||||
assert_eq!(batches[2].len(), 1);
|
||||
assert_eq!(batches[3].len(), 2);
|
||||
assert!(batches[1][0].should_send_in_isolated_request());
|
||||
assert!(batches[2][0].should_send_in_isolated_request());
|
||||
}
|
||||
Reference in new issue
Block a user