mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex-analytics] add extensible feature thread sources (#27063)
## Why - `ThreadSource` currently defines a closed set of core-owned values - Product features also create threads for background or scheduled work - Adding every product-specific value to the core enum would require repeated `codex-rs` protocol changes - Feature-backed values let product callers provide precise attribution while preserving the existing core classifications ## What Changed - Adds `ThreadSource::Feature(String)` for app-owned thread source values - Represents all app-server v2 thread sources as scalar strings, so a feature source is supplied as `"automation"` - Persists and emits the feature's plain string label, so `"automation"` produces `thread_source="automation"` in analytics - Keeps `user`, `subagent`, and `memory_consolidation` as explicit core-owned values and regenerates the app-server schemas and TypeScript bindings ## Verification - `just write-app-server-schema` - `cargo check --workspace` - `just test -p codex-protocol feature_thread_source_serializes_as_its_app_owned_label` - `just test -p codex-app-server-protocol thread_sources_round_trip_as_scalar_labels` - `cargo test -p codex-analytics thread_initialized_event_serializes_expected_shape` - `just fmt`
This commit is contained in:
committed by
GitHub
Unverified
parent
99da697e4c
commit
a71e040df5
@@ -57,6 +57,30 @@ fn test_absolute_path() -> AbsolutePathBuf {
|
||||
absolute_path("readable")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_sources_round_trip_as_scalar_labels() {
|
||||
for (source, label) in [
|
||||
(ThreadSource::User, "user"),
|
||||
(ThreadSource::Subagent, "subagent"),
|
||||
(
|
||||
ThreadSource::Feature("automation".to_string()),
|
||||
"automation",
|
||||
),
|
||||
(ThreadSource::MemoryConsolidation, "memory_consolidation"),
|
||||
] {
|
||||
let value = serde_json::to_value(&source).expect("serialize thread source");
|
||||
|
||||
assert_eq!(value, json!(label));
|
||||
assert_eq!(
|
||||
serde_json::from_value::<ThreadSource>(value).expect("deserialize thread source"),
|
||||
source
|
||||
);
|
||||
|
||||
let core_source: codex_protocol::protocol::ThreadSource = source.clone().into();
|
||||
assert_eq!(ThreadSource::from(core_source), source);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approvals_reviewer_serializes_auto_review_and_accepts_legacy_guardian_subagent() {
|
||||
assert_eq!(
|
||||
|
||||
@@ -7,6 +7,8 @@ use codex_protocol::protocol::SubAgentSource as CoreSubAgentSource;
|
||||
use codex_protocol::protocol::ThreadSource as CoreThreadSource;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use schemars::JsonSchema;
|
||||
use schemars::r#gen::SchemaGenerator;
|
||||
use schemars::schema::Schema;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
@@ -61,20 +63,47 @@ impl From<SessionSource> for CoreSessionSource {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(rename_all = "snake_case", export_to = "v2/")]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, TS)]
|
||||
#[serde(try_from = "String", into = "String")]
|
||||
#[ts(type = "string")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum ThreadSource {
|
||||
User,
|
||||
Subagent,
|
||||
Feature(String),
|
||||
MemoryConsolidation,
|
||||
}
|
||||
|
||||
impl JsonSchema for ThreadSource {
|
||||
fn schema_name() -> String {
|
||||
"ThreadSource".to_string()
|
||||
}
|
||||
|
||||
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
|
||||
String::json_schema(generator)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ThreadSource {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
value.parse::<CoreThreadSource>().map(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ThreadSource> for String {
|
||||
fn from(value: ThreadSource) -> Self {
|
||||
CoreThreadSource::from(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CoreThreadSource> for ThreadSource {
|
||||
fn from(value: CoreThreadSource) -> Self {
|
||||
match value {
|
||||
CoreThreadSource::User => ThreadSource::User,
|
||||
CoreThreadSource::Subagent => ThreadSource::Subagent,
|
||||
CoreThreadSource::Feature(feature) => ThreadSource::Feature(feature),
|
||||
CoreThreadSource::MemoryConsolidation => ThreadSource::MemoryConsolidation,
|
||||
}
|
||||
}
|
||||
@@ -85,6 +114,7 @@ impl From<ThreadSource> for CoreThreadSource {
|
||||
match value {
|
||||
ThreadSource::User => CoreThreadSource::User,
|
||||
ThreadSource::Subagent => CoreThreadSource::Subagent,
|
||||
ThreadSource::Feature(feature) => CoreThreadSource::Feature(feature),
|
||||
ThreadSource::MemoryConsolidation => CoreThreadSource::MemoryConsolidation,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user